Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> prevail, in peace and freedom from fear, and in true
* health, through the purity and essence of our natural... fluids.
* - General Turgidson
*/
class PureFunctionIdentifier implements CompilerPass {
static final DiagnosticType INVALID_NO_SIDE_EFFECT_ANNOTATION =
DiagnosticType.error(
"JSC_INVALID_NO_SIDE_EFFECT_ANNOTATION",
"@nosideeffects may only appear in externs files.");
private final AbstractCompiler compiler;
private final DefinitionProvider definitionProvider;
// Function node -> function side effects map
private final Map<Node, FunctionInformation> functionSideEffectMap;
// List of all function call sites; used to iterate in markPureFunctionCalls.
private final List<Node> allFunctionCalls;
// Externs and ast tree root, for use in getDebugReport. These two
// fields are null until process is called.
private Node externs;
private Node root;
public PureFunctionIdentifier(AbstractCompiler compiler,
DefinitionProvider definitionProvider) {
this.compiler = compiler;
this.definitionProvider = definitionProvider;
this.functionSideEffectMap = Maps.newHashMap();
this.allFunctionCalls = Lists.newArrayList();
this.externs = null;
this.root = null;
}
@Override
public void process(Node externsAst, Node srcAst) {
if (externs != null || root != null) {
throw new IllegalStateException(
"It is illegal to call PureFunctionIdentifier.process " +
"twice the same instance. Please use a new " +
"PureFunctionIdentifier instance each time.");
}
externs = externsAst;
root = srcAst;
NodeTraversal.traverse(compiler, externs, new FunctionAnalyzer(true));
NodeTraversal.traverse(compiler, root, new FunctionAnalyzer(false));
propagateSideEffects();
markPureFunctionCalls();
}
/**
* Compute debug report that includes:
* - List of all pure functions.
* - Reasons we think the remaining functions have side effects.
*/
String getDebugReport() {
Preconditions.checkNotNull(externs);
Preconditions.checkNotNull(root);
StringBuilder sb = new StringBuilder();
FunctionNames functionNames = new FunctionNames(compiler);
functionNames.process(null,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> for (Definition def : defs) {
Node defValue = def.getRValue();
FunctionInformation dep = functionSideEffectMap.get(defValue);
Preconditions.checkNotNull(dep);
sideEffectGraph.connect(dep, callSite, functionInfo);
}
}
}
// Propagate side effect information to a fixed point.
FixedPointGraphTraversal.newTraversal(new SideEffectPropagationCallback())
.computeFixedPoint(sideEffectGraph);
// Mark remaining functions "pure".
for (FunctionInformation functionInfo : functionSideEffectMap.values()) {
if (functionInfo.mayBePure()) {
functionInfo.setIsPure();
}
}
}
/**
* Set no side effect property at pure-function call sites.
*/
private void markPureFunctionCalls() {
for (Node callNode : allFunctionCalls) {
Node name = callNode.getFirstChild();
Collection<Definition> defs =
getCallableDefinitions(definitionProvider, name);
if (defs == null) {
continue;
}
boolean hasSideEffects = false;
for (Definition def : defs) {
FunctionInformation functionInfo =
functionSideEffectMap.get(def.getRValue());
Preconditions.checkNotNull(functionInfo);
if ((NodeUtil.isCall(callNode) && functionInfo.mayHaveSideEffects()) ||
(NodeUtil.isNew(callNode) && (functionInfo.mutatesGlobalState() ||
functionInfo.functionThrows()))) {
hasSideEffects = true;
break;
}
}
if (!hasSideEffects) {
callNode.setIsNoSideEffectsCall();
}
}
}
/**
* Gather list of functions, functions with @nosideeffect
* annotations, call sites, and functions that may mutate variables
* not defined in the local scope.
*/
private class FunctionAnalyzer implements Callback {
private final boolean inExterns;
FunctionAnalyzer(boolean inExterns) {
this.inExterns = inExterns;
}
@Override
public boolean shouldTraverse(NodeTraversal traversal,
Node node,
Node parent) {
// Functions need to be processed as part of pre-traversal so an
// entry for the enclosing function exists in the
// FunctionInformation map when processing assignments and calls
// inside visit.
if (
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>NodeUtil.isFunction(node)) {
Node gramp = parent.getParent();
visitFunction(traversal, node, parent, gramp);
}
return true;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (inExterns) {
return;
}
if (!NodeUtil.nodeTypeMayHaveSideEffects(node)) {
return;
}
if (NodeUtil.isCall(node) || NodeUtil.isNew(node)) {
allFunctionCalls.add(node);
}
Node enclosingFunction = traversal.getEnclosingFunction();
if (enclosingFunction != null) {
FunctionInformation sideEffectInfo =
functionSideEffectMap.get(enclosingFunction);
Preconditions.checkNotNull(sideEffectInfo);
if (NodeUtil.isAssignmentOp(node)) {
visitAssignmentOrUnaryOperatorLhs(
sideEffectInfo, traversal.getScope(), node.getFirstChild());
} else {
switch(node.getType()) {
case Token.CALL:
case Token.NEW:
visitCall(sideEffectInfo, node);
break;
case Token.DELPROP:
case Token.DEC:
case Token.INC:
visitAssignmentOrUnaryOperatorLhs(
sideEffectInfo, traversal.getScope(), node.getFirstChild());
break;
case Token.NAME:
// Variable definition are not side effects.
// Just check that the name appears in the context of a
// variable declaration.
Preconditions.checkArgument(
NodeUtil.isVarDeclaration(node));
break;
case Token.THROW:
visitThrow(sideEffectInfo);
break;
default:
throw new IllegalArgumentException(
"Unhandled side effect node type " +
Token.name(node.getType()));
}
}
}
}
/**
* Record information about the side effects caused by an
* assigment or mutating unary operator.
*
* If the operation modifies this or taints global state, mark the
* enclosing function as having those side effects.
*/
private void visitAssignmentOrUnaryOperatorLhs(
FunctionInformation sideEffectInfo, Scope scope, Node lhs) {
if (NodeUtil.isName(lhs)) {
Var var = scope.getVar(lhs.getString());
if (var == null ||
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> var.scope != scope) {
sideEffectInfo.setTaintsGlobalState();
}
} else if (NodeUtil.isGetProp(lhs)) {
if (NodeUtil.isThis(lhs.getFirstChild())) {
sideEffectInfo.setTaintsThis();
} else {
sideEffectInfo.setTaintsUnknown();
}
} else {
sideEffectInfo.setTaintsUnknown();
}
}
/**
* Record information about a call site.
*/
private void visitCall(FunctionInformation sideEffectInfo, Node node) {
sideEffectInfo.appendCall(node);
}
/**
* Record function and check for @nosideeffects annotations.
*/
private void visitFunction(NodeTraversal traversal,
Node node,
Node parent,
Node gramp) {
Preconditions.checkArgument(!functionSideEffectMap.containsKey(node));
FunctionInformation sideEffectInfo = new FunctionInformation(inExterns);
functionSideEffectMap.put(node, sideEffectInfo);
if (hasNoSideEffectsAnnotation(node, parent, gramp)) {
if (inExterns) {
sideEffectInfo.setIsPure();
} else {
traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
} else if (inExterns) {
sideEffectInfo.setTaintsGlobalState();
}
}
/**
* Record that the enclosing function throws.
*/
private void visitThrow(FunctionInformation sideEffectInfo) {
sideEffectInfo.setFunctionThrows();
}
/**
* Get the value of the @nosideeffects annotation stored in the
* doc info.
*/
private boolean hasNoSideEffectsAnnotation(Node node,
Node parent,
Node gramp) {
{
JSDocInfo docInfo = node.getJSDocInfo();
if (docInfo != null && docInfo.isNoSideEffects()) {
return true;
}
}
if (NodeUtil.isName(parent)) {
JSDocInfo docInfo = gramp.getJSDocInfo();
return gramp.hasOneChild() &&
docInfo != null &&
docInfo.isNoSideEffects();
} else if (NodeUtil.isAssign(parent)) {
JSDocInfo docInfo = parent
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.getJSDocInfo();
return docInfo != null && docInfo.isNoSideEffects();
} else {
return false;
}
}
}
/**
* Callback that propagates side effect information across call sites.
*/
private static class SideEffectPropagationCallback
implements EdgeCallback<FunctionInformation, Node> {
public boolean traverseEdge(FunctionInformation callee,
Node callSite,
FunctionInformation caller) {
Preconditions.checkArgument(callSite.getType() == Token.CALL ||
callSite.getType() == Token.NEW);
boolean changed = false;
if (!caller.mutatesGlobalState() && callee.mutatesGlobalState()) {
caller.setTaintsGlobalState();
changed = true;
}
if (!caller.functionThrows() && callee.functionThrows()) {
caller.setFunctionThrows();
changed = true;
}
if (callee.mutatesThis()) {
// Side effects only propagate via regular calls.
// Calling a constructor that modifies "this" has no side effects.
if (callSite.getType() != Token.NEW) {
Node objectNode = getCallThisObject(callSite);
if (objectNode != null && NodeUtil.isThis(objectNode)) {
if (!caller.mutatesThis()) {
caller.setTaintsThis();
changed = true;
}
} else if (!caller.mutatesGlobalState()) {
caller.setTaintsGlobalState();
changed = true;
}
}
}
return changed;
}
}
/**
* Analyze a call site and extract the node that will be act as
* "this" inside the call, which is either the object part of the
* qualified function name, the first argument to the call in the
* case of ".call" and ".apply" or null if object is not specified
* in either of those ways.
*
* @return node that will act as "this" for the call.
*/
private static Node getCallThisObject(Node callSite) {
Node foo = callSite.getFirstChild();
if (!NodeUtil.isGetProp(foo)) {
// "this" is not specified explicitly; call modifies global "this".
return null;
}
Node object = null;
String propString = foo.getLast
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.LP:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null)
return true;
break;
case Token.FUNCTION:
// Anonymous functions don't have side-effects, but named ones
// change the namespace. Therefore, we check if the function has
// a name. Either way, we don't need to check the children, since
// they aren't executed at declaration time.
//
return !isFunctionAnonymous(n);
case Token.NEW:
{
if (checkForNewObjects) {
return true;
}
// calls to constructors that have no side effects have the
// no side effect property set.
if (n.isNoSideEffectsCall()) {
break;
}
// certain constructors are certified side effect free
Node constructor = n.getFirstChild();
if (Token.NAME == constructor.getType()) {
String className = constructor.getString();
if (CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(className)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
} else {
// the constructor could also be an expression like
// new (useArray ? Object : Array)();
}
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
// side effect property set.
if (n.isNoSideEffectsCall()) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperatorType(n.getType()))
break;
if (isAssignmentOp(n)) {
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects)) {
return true;
}
Node current = n.getFirstChild();
for (;
current.getType() == Token.GETPROP ||
current.getType() == Token.GETELEM;
current = current.getFirstChild()) { }
return !(isLiteralValue(current) ||
current.getType() == Token.FUNCTION);
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - construtor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
Preconditions.checkArgument(
callNode.getType() == Token.NEW,
"Expected NEW node, got " + Token.name(callNode.getType()));
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.getType() == Token.NAME &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode - function call node
*/
static boolean functionCallHasSideEffects(Node callNode) {
Preconditions.checkArgument(
callNode.getType() == Token.CALL,
"Expected CALL node,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>wise-and &
* 8 equality == !=
* 9 relational < <= > >=
* 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
* 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.LSH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.ARRAYLIT:
case Token.CALL:
case Token
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.EMPTY:
case Token.FALSE:
case Token.FUNCTION:
case Token.GETELEM:
case Token.GETPROP:
case Token.GET_REF:
case Token.IF:
case Token.LP:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.RETURN:
case Token.STRING:
case Token.THIS:
case Token.TRUE:
return 15;
default: throw new Error("Unknown precedence for " +
Node.tokenToName(type) +
" (type " + type + ")");
}
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatentation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assiment op");
}
static boolean isExpressionNode(Node n) {
return n.getType() == Token.EXPR_RESULT;
}
/**
* Determines if the given node contains a function declaration.
*/
static boolean containsFunctionDeclaration(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the subtree contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
return containsType(n, Token.THIS);
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.getType() == Token.GETPROP
|| n.getType() == Token.GETELEM;
}
/**
* Is this a GETPROP node?
*/
static boolean isGetProp(Node n) {
return n.getType() == Token.GETPROP;
}
/**
* Is this a NAME node?
*/
static boolean isName(Node n) {
return n.getType() == Token.NAME;
}
/**
* Is this a NEW node?
*/
static boolean isNew(Node n) {
return n.getType() == Token.NEW;
}
/**
* Is this a VAR node?
*/
static boolean isVar(Node n) {
return n.getType() == Token.VAR;
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.getType() == Token
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.NAME && n.getParent().getType() == Token.VAR;
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(isName(n));
Node parent = n.getParent();
if (isVar(parent)) {
return n.getFirstChild();
} else if (isAssign(parent) && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this a STRING node?
*/
static boolean isString(Node n) {
return n.getType() == Token.STRING;
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN;
}
/**
* Is this an ASSIGN node?
*/
static boolean isAssign(Node n) {
return n.getType() == Token.ASSIGN;
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.CALL;
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.getType() == Token.FOR
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> /**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK;
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
Node parent = n.getParent();
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node, for instance,
// is either part of an expression (as a anonymous function) or as
// a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.getType() == Token.CASE || n.getType() == Token.DEFAULT;
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty anonymous function name).
*/
static boolean isReferenceName(Node n) {
return isName(n) && !n.getString().isEmpty() && !isLabelName(n);
}
/** @return Whether the node is a label name. */
static boolean isLabelName(Node n) {
if (n != null && n.getType() == Token.NAME) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.LABEL:
case Token
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.getType() == Token.BLOCK);
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (NodeUtil.isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else if (parent.getType() == Token.LABEL && block.hasOneChild()) {
parent.replaceChild(block, block.removeFirstChild());
return true;
} else {
return false;
}
}
/**
* Is this a CALL node?
*/
static boolean isCall(Node n) {
return n.getType() == Token.CALL;
}
/**
* Is this a FUNCTION node?
*/
static boolean isFunction(Node n) {
return n.getType() == Token.FUNCTION;
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(isFunction(fn));
return fn.getLastChild();
}
/**
* Is this a THIS node?
*/
static boolean isThis(Node node) {
return node.getType() == Token.THIS;
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not anonymous; see {@link #isFunctionAnonymous}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.getType() == Token.FUNCTION && !isFunctionAnonymous(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(Node n) {
return NodeUtil.isFunctionDeclaration(n)
&& (n.getParent().getType() == Token.SCRIPT
|| n.getParent().getParent().getType() == Token.FUNCTION);
}
/**
* Is this node an anonymous function? An anonymous function is one that has
* either no name or a name that is not added to the current scope (see
* {@link #isFunctionAnonymous}).
*/
static boolean isAnonymousFunction(Node n) {
return n.getType() == Token.FUNCTION && isFunctionAnonymous(n);
}
/**
* Is a FUNCTION node an anonymous function? An anonymous function is one that
* has either no name or a name that is not added to the current scope.
*
* <p>Some examples of anonymous functions:
* <pre>
* function () {}
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> anonymous:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A FUNCTION node
* @return Whether n is an anonymous function
*/
static boolean isFunctionAnonymous(Node n) {
return !isStatement(n);
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
Preconditions.checkArgument(isFunction(function));
return NodeUtil.isNameReferenced(
function.getLastChild(),
"arguments",
Predicates.<Node>not(new NodeUtil.MatchNodeType(Token.FUNCTION)));
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.getType() == Token.CALL) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (NodeUtil.isGet(functionIndentifyingExpression)) {
Node last =
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>LIT) {
int index = 0;
for (Node current = parent.getFirstChild();
current != null;
current = current.getNext()) {
if (current == node) {
return index % 2 == 0;
}
index++;
}
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
case Token.OR: return "||";
case Token.BITXOR: return "^";
case Token.AND: return "&&";
case Token.BITAND: return "&";
case Token.SHEQ: return "===";
case Token.EQ: return "==";
case Token.NOT: return "!";
case Token.NE: return "!=";
case Token.SHNE: return "!==";
case Token.LSH: return "<<";
case Token.IN: return "in";
case Token.LE: return "<=";
case Token.LT: return "<";
case Token.URSH: return ">>>";
case Token.RSH: return ">>";
case Token.GE: return ">=";
case Token.GT: return ">";
case Token.MUL: return "*";
case Token.DIV: return "/";
case Token.MOD: return "%";
case Token.BITNOT: return "~";
case Token.ADD: return "+";
case Token.SUB: return "-";
case Token.POS: return "+";
case Token.NEG: return "-";
case Token.ASSIGN: return "=";
case Token.ASSIGN_BITOR: return "|=";
case Token.ASSIGN_BITXOR: return "^=";
case Token.ASSIGN_BITAND: return "&=";
case Token.ASSIGN_LSH: return "<<=";
case Token.ASSIGN_RSH: return ">>=";
case Token.ASSIGN_URSH: return ">>>=";
case Token.ASSIGN_ADD: return "+=";
case Token.ASSIGN_SUB
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>: return "-=";
case Token.ASSIGN_MUL: return "*=";
case Token.ASSIGN_DIV: return "/=";
case Token.ASSIGN_MOD: return "%=";
case Token.VOID: return "void";
case Token.TYPEOF: return "typeof";
case Token.INSTANCEOF: return "instanceof";
default: return null;
}
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation or fails.
*
* @param operator the operator's token value to convert
* @return the string representation
* @throws Error if the token value is not an operator
*/
static String opToStrNoFail(int operator) {
String res = opToStr(operator);
if (res == null) {
throw new Error("Unknown op " + operator + ": " +
Token.name(operator));
}
return res;
}
/**
* @return true if n or any of its children are of the specified type.
* Does not traverse into functions.
*/
static boolean containsTypeInOuterScope(Node node, int type) {
return containsType(node, type,
Predicates.<Node>not(new NodeUtil.MatchNodeType(Token.FUNCTION)));
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node,
int type,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>) {
Node var = new Node(
Token.VAR, Node.newString(Token.NAME, nameNode.getString()));
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.getType() == Token.BLOCK ||
addingRoot.getType() == Token.SCRIPT);
Preconditions.checkState(addingRoot.getFirstChild() == null ||
addingRoot.getFirstChild().getType() != Token.SCRIPT);
return addingRoot;
}
/** Creates function name(params_0, ..., params_n) { body }. */
public static FunctionNode newFunctionNode(String name, List<Node> params,
Node body, int lineno, int charno) {
Node parameterParen = new Node(Token.LP, lineno, charno);
for (Node param : params) {
parameterParen.addChildToBack(param);
}
FunctionNode function = new FunctionNode(name, lineno, charno);
function.addChildrenToBack(
Node.newString(Token.NAME, name, lineno, charno));
function.addChildToBack(parameterParen);
function.addChildToBack(body);
return function;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>7f;
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
NodeUtil.isLatin(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
public void visit(Node n) {
if (n.getType() == Token.NAME) {
Node parent = n.getParent();
if (parent != null && parent.getType() == Token.VAR) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
public static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
Predicates.<Node>not(new NodeUtil.MatchNodeType(Token.FUNCTION)));
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
if (!NodeUtil.isExprAssign(n)) {
return false;
}
return isPrototypeProperty(n.getFirstChild().getFirstChild());
}
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
if (lhsString ==
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> if (pred.apply(n)) {
total++;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
total += getCount(c, pred);
}
return total;
}
/**
* Interface for use with the visit method.
* @see #visit
*/
static interface Visitor {
void visit(Node node);
}
/**
* A pre-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPreOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
vistor.visit(node);
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPreOrder(c, vistor, traverseChildrenPred);
}
}
}
/**
* A post-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPostOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPostOrder(c, vistor, traverseChildrenPred);
}
}
vistor.visit(node);
}
/**
* @return Whether a TRY node has a finally block.
*/
static boolean hasFinally(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY);
return n.getChildCount() == 3;
}
/**
* @return The BLOCK node containing the CATCH node (if any)
* of a TRY.
*/
static Node getCatchBlock(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY);
return n.getFirstChild().getNext();
}
/**
* @return Whether BLOCK (from a TRY node) contains a CATCH.
* @see NodeUtil#getCatchBlock
*/
static boolean hasCatchHandler(Node n) {
Preconditions.checkArgument(n.getType() == Token.BLOCK);
return n.hasChildren() && n.getFirstChild().getType() == Token.CATCH
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
static Node getFnParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION);
return fnNode.getFirstChild().getNext();
}
/**
* Returns true if a name node represents a constant variable.
*
* <p>Determining whether a variable is constant has three steps:
* <ol>
* <li>In CodingConventionAnnotator, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* <li>The normalize pass renames any variable with the IS_CONSTANT_NAME
* annotation and that is initialized to a constant value with
* a variable name inlucding $$constant.
* <li>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/**
* @param nameNode A name node
* @return The JSDocInfo for the name node
*/
static JSDocInfo getInfoForNameNode(Node nameNode) {
JSDocInfo info = null;
Node parent = null;
if (nameNode != null) {
info = nameNode.getJSDocInfo();
parent = nameNode.getParent();
}
if (info == null && parent != null &&
((parent.getType() == Token.VAR && parent.hasOneChild()) ||
parent.getType() == Token.FUNCTION)) {
info = parent.getJSDocInfo();
}
return info;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = (String) n.getProp(Node.SOUR
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> Id_export = Token.EXPORT,
Id_false = Token.FALSE,
Id_for = Token.FOR,
Id_function = Token.FUNCTION,
Id_if = Token.IF,
Id_in = Token.IN,
Id_new = Token.NEW,
Id_null = Token.NULL,
Id_return = Token.RETURN,
Id_switch = Token.SWITCH,
Id_this = Token.THIS,
Id_true = Token.TRUE,
Id_typeof = Token.TYPEOF,
Id_var = Token.VAR,
Id_void = Token.VOID,
Id_while = Token.WHILE,
Id_with = Token.WITH,
// the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c
Id_abstract = Token.RESERVED,
Id_boolean = Token.RESERVED,
Id_byte = Token.RESERVED,
Id_catch = Token.CATCH,
Id_char = Token.RESERVED,
Id_class = Token.RESERVED,
Id_const = Token.CONST,
Id_debugger = Token.DEBUGGER,
Id_double = Token.RESERVED,
Id_enum = Token.RESERVED,
Id_extends = Token.RESERVED,
Id_final = Token.RESERVED,
Id_finally = Token.FINALLY,
Id_float = Token.RESERVED,
Id_goto = Token.RESERVED,
Id_implements = Token.RESERVED,
Id_import = Token.IMPORT,
Id_instanceof = Token.INSTANCEOF,
Id_int = Token.RESERVED,
Id_interface = Token.RESERVED,
Id_long = Token.RESERVED,
Id_native = Token.RESERVED,
Id_package = Token.RESERVED,
Id_private = Token.RESERVED,
Id_protected = Token.RESERVED,
Id_public = Token.RESERVED,
Id_short = Token.RESERVED,
Id_static = Token.RESERVED,
Id_super = Token.RESERVED,
Id_synchronized = Token.RESERVED,
Id_throw = Token.THROW,
Id_
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>();
escapeVal = Kit.xDigitToInt(c, 0);
if (escapeVal < 0) {
addToString('x');
continue strLoop;
} else {
int c1 = c;
c = getChar();
escapeVal = Kit.xDigitToInt(c, escapeVal);
if (escapeVal < 0) {
addToString('x');
addToString(c1);
continue strLoop;
} else {
// got 2 hex digits
c = escapeVal;
}
}
break;
case '\n':
// Remove line terminator after escape to follow
// SpiderMonkey and C/C++
c = getChar();
continue strLoop;
default:
if ('0' <= c && c < '8') {
int val = c - '0';
c = getChar();
if ('0' <= c && c < '8') {
val = 8 * val + c - '0';
c = getChar();
if ('0' <= c && c < '8' && val <= 037) {
// c is 3rd char of octal sequence only
// if the resulting val <= 0377
val = 8 * val + c - '0';
c = getChar();
}
}
ungetChar(c);
c = val;
}
}
}
addToString(c);
c = getChar();
}
String str = getStringFromBuffer();
this.string = (String)allStrings.intern(str);
return Token.STRING;
}
switch (c) {
case ';': return Token.SEMI;
case '[': return Token.LB;
case ']': return Token.RB;
case '{': return Token.LC;
case '}': return Token.RC;
case '(': return Token.LP;
case ')': return Token.RP;
case ',': return Token.COMMA;
case '?': return Token.HOOK;
case ':':
if (matchChar(':')) {
return Token.COLONCOLON;
} else {
return Token.COLON;
}
case '.':
if (matchChar('.')) {
return Token.DOTDOT;
} else if (matchChar('('))
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> {
return Token.DOTQUERY;
} else {
return Token.DOT;
}
case '|':
if (matchChar('|')) {
return Token.OR;
} else if (matchChar('=')) {
return Token.ASSIGN_BITOR;
} else {
return Token.BITOR;
}
case '^':
if (matchChar('=')) {
return Token.ASSIGN_BITXOR;
} else {
return Token.BITXOR;
}
case '&':
if (matchChar('&')) {
return Token.AND;
} else if (matchChar('=')) {
return Token.ASSIGN_BITAND;
} else {
return Token.BITAND;
}
case '=':
if (matchChar('=')) {
if (matchChar('='))
return Token.SHEQ;
else
return Token.EQ;
} else {
return Token.ASSIGN;
}
case '!':
if (matchChar('=')) {
if (matchChar('='))
return Token.SHNE;
else
return Token.NE;
} else {
return Token.NOT;
}
case '<':
/* NB:treat HTML begin-comment as comment-till-eol */
if (matchChar('!')) {
if (matchChar('-')) {
if (matchChar('-')) {
skipLine();
continue retry;
}
ungetChar('-');
}
ungetChar('!');
}
if (matchChar('<')) {
if (matchChar('=')) {
return Token.ASSIGN_LSH;
} else {
return Token.LSH;
}
} else {
if (matchChar('=')) {
return Token.LE;
} else {
return Token.LT;
}
}
case '>':
if (matchChar('>')) {
if (matchChar('>')) {
if (matchChar('=')) {
return Token.ASSIGN_URSH;
} else {
return Token.URSH;
}
} else {
if (matchChar('=')) {
return Token.ASSIGN_RSH;
} else {
return Token.RSH;
}
}
} else {
if (matchChar('=')) {
return Token.GE;
} else
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> declared names.
Preconditions.checkState(parent.getType() != Token.FUNCTION
|| parent.getType() != Token.VAR
|| parent.getType() != Token.CATCH);
// The name may need to be replaced more than once,
// so we need to clone the node.
Node replacement = replacementTemplate.cloneTree();
parent.replaceChild(node, replacement);
return replacement;
}
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
// We have to reassign c in case it was replaced, because the removed c's
// getNext() would no longer be correct.
c = inject(c, node, replacements);
}
return node;
}
/**
* Get a mapping for function parameter names to call arguments.
*/
static LinkedHashMap<String, Node> getFunctionCallParameterMap(
Node fnNode, Node callNode, Supplier<String> safeNameIdSupplier) {
// Create an argName -> expression map
// NOTE: A linked map is created here to provide ordering.
LinkedHashMap<String, Node> argMap = Maps.newLinkedHashMap();
// CALL NODE: [ NAME, ARG1, ARG2, ... ]
Node cArg = callNode.getFirstChild().getNext();
if (callNode.getFirstChild().getType() != Token.NAME) {
if (NodeUtil.isFunctionObjectCall(callNode)) {
// TODO(johnlenz): Support replace this with a value.
Preconditions.checkNotNull(cArg);
Preconditions.checkState(cArg.getType() == Token.THIS);
cArg = cArg.getNext();
} else {
Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode));
}
}
for (Node fnArg : NodeUtil.getFnParameters(fnNode).children()) {
if (cArg != null) {
argMap.put(fnArg.getString(), cArg);
cArg = cArg.getNext();
} else {
argMap.put(fnArg.getString(), NodeUtil.newUndefinedNode());
}
}
// Add temp names for arguments that don't have named parameters in the
// called function.
int anonArg = 0;
while (cArg != null) {
String uniquePlaceholder =
getUniqueAnonymousParameterName(safe
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>NameIdSupplier);
argMap.put(uniquePlaceholder, cArg);
cArg = cArg.getNext();
}
return argMap;
}
/**
* Parameter names will be name unique when at a later time.
*/
private static String getUniqueAnonymousParameterName(
Supplier<String> safeNameIdSupplier) {
return "JSCompiler_inline_anon_param_" + safeNameIdSupplier.get();
}
/**
* Retrieve a set of names that can not be safely substituted in place.
* Example:
* function(a) {
* a = 0;
* }
* Inlining this without taking precautions would cause the call site value
* to be modified (bad).
*/
static Set<String> findModifiedParameters(Node fnNode) {
Set<String> names = getFunctionParameterSet(fnNode);
Set<String> unsafeNames = Sets.newHashSet();
return findModifiedParameters(
fnNode, null, names, unsafeNames);
}
/**
* Check for uses of the named value that imply a pass-by-value
* parameter is expected. This is used to prevent cases like:
*
* function (x) {
* x=2;
* return x;
* }
*
* We don't want "undefined" to be substituted for "x", and get
* undefined=2
*
* @param n The node in question.
* @param parent The parent of the node.
* @param names The set of names to check.
*/
private static Set<String> findModifiedParameters(
Node n, Node parent, Set<String> names, Set<String> unsafe) {
Preconditions.checkArgument(unsafe != null);
if (n.getType() == Token.NAME) {
if (names.contains(n.getString())) {
if (canNameValueChange(n, parent)) {
unsafe.add(n.getString());
}
}
}
for (Node c : n.children()) {
findModifiedParameters(c, n, names, unsafe);
}
return unsafe;
}
/**
* This is similar to NodeUtil.isLValue except that object properties and
* array member modification aren't important ("o" in "
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>o.a = 2" is still "o"
* after assignment, where in as "o = x", "o" is now "x").
*
* This also looks for the redefinition of a name.
* function (x){var x;}
*
* @param n The NAME node in question.
* @param parent The parent of the node.
*/
private static boolean canNameValueChange(Node n, Node parent) {
int type = parent.getType();
return (type == Token.VAR || type == Token.INC || type == Token.DEC ||
(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n));
}
/**
* Updates the set of parameter names in set unsafe to include any
* arguments from the call site that require aliases.
* @param fnNode The FUNCTION node to be inlined.
* @param argMap The argument list for the call to fnNode.
* @param namesNeedingTemps The set of names to update.
*/
static void maybeAddTempsForCallArguments(
Node fnNode, Map<String, Node> argMap, Set<String> namesNeedingTemps,
CodingConvention convention) {
if (argMap.isEmpty()) {
// No arguments to check, we are done.
return;
}
Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION);
Node block = fnNode.getLastChild();
Set<String> parameters = argMap.keySet();
// Get the list of parameters that may need temporaries due to
// side-effects.
Set<String> namesAfterSideEffects = findParametersReferencedAfterSideEffect(
parameters, block);
// Check for arguments that are evaluated more than once.
for (Map.Entry<String, Node> entry : argMap.entrySet()) {
String argName = entry.getKey();
if (namesNeedingTemps.contains(argName)) {
continue;
}
Node cArg = entry.getValue();
boolean safe = true;
int references = NodeUtil.getNameReferenceCount(block, argName);
if (NodeUtil.mayEffectMutableState(cArg) && references > 0) {
// Note: Mutable arguments should be assigned to temps, as the
// may be within in a loop:
//
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>();
}
}
if (!sideEffectSeen) {
// Look for side-effects.
if (hasNonLocalSideEffect(n)) {
sideEffectSeen = true;
}
}
// If traversing the nodes of a loop save any references
// that are seen.
if (inLoop() || sideEffectSeen) {
// Record references to parameters.
if (n.getType() == Token.NAME) {
String name = n.getString();
if (parameters.contains(name)) {
parametersReferenced.add(name);
}
}
}
}
/**
* @return Whether the node may have non-local side-effects.
*/
private boolean hasNonLocalSideEffect(Node n) {
boolean sideEffect = false;
int type = n.getType();
// Note: Only care about changes to non-local names, specifically
// ignore VAR declaration assignments.
if (NodeUtil.isAssignmentOp(n)
|| type == Token.INC
|| type == Token.DEC) {
Node lhs = n.getFirstChild();
// Ignore changes to local names.
if (!isLocalName(lhs)) {
sideEffect = true;
}
} else if (type == Token.CALL) {
sideEffect = NodeUtil.functionCallHasSideEffects(n);
} else if (type == Token.NEW) {
sideEffect = NodeUtil.constructorCallHasSideEffects(n);
} else if (type == Token.DELPROP) {
sideEffect = true;
}
return sideEffect;
}
/**
* @return Whether node is a reference to locally declared name.
*/
private boolean isLocalName(Node node) {
if (NodeUtil.isName(node)) {
String name = node.getString();
return locals.contains(name);
}
return false;
}
}
/**
* Gather any names declared in the local scope.
*/
private static void gatherLocalNames(Node n, Set<String> names) {
Preconditions.checkState(n.getType() != Token.FUNCTION);
if (n.getType() == Token.NAME) {
switch (n.getParent().getType()) {
case Token.VAR:
case Token.CATCH:
names.add(n.getString());
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> is relative only to the current
* run of the CodeConsumer and will be normalized
* later on by the SourceMap.
*
* @see SourceMap
*/
private static class Mapping {
Node node;
Position start;
Position end;
}
/**
* Starts the source mapping for the given
* node at the current position.
*/
@Override
void startSourceMapping(Node node) {
if (createSrcMap
&& node.getProp(Node.SOURCEFILE_PROP) != null
&& node.getLineno() > 0) {
int line = getCurrentLineIndex();
int index = getCurrentCharIndex();
// If the index is -1, we are not performing any mapping.
if (index >= 0) {
Mapping mapping = new Mapping();
mapping.node = node;
mapping.start = new Position(line, index);
mappings.push(mapping);
allMappings.add(mapping);
}
}
}
/**
* Finishes the source mapping for the given
* node at the current position.
*/
@Override
void endSourceMapping(Node node) {
if (createSrcMap
&& node.getProp(Node.SOURCEFILE_PROP) != null
&& node.getLineno() > 0) {
int line = getCurrentLineIndex();
int index = getCurrentCharIndex();
// If the index is -1, we are not performing any mapping.
if (index >= 0) {
Preconditions.checkState(
!mappings.empty(), "Mismatch in start and end of mapping");
Mapping mapping = mappings.pop();
mapping.end = new Position(line, index);
}
}
}
/**
* Generates the source map from the given code consumer,
* appending the information it saved to the SourceMap
* object given.
*/
@Override
void generateSourceMap(SourceMap map){
if (createSrcMap) {
for (Mapping mapping : allMappings) {
map.addMapping(mapping.node, mapping.start, mapping.end);
}
}
}
/**
* Reports to the code consumer that the given line has been cut at the
* given position (i.e. a \n has been inserted there). All mappings in
* the source maps after that position will be renormalized
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) {
typeOfNode = left;
stringNode = right;
} else if (right.getType() == Token.TYPEOF &&
left.getType() == Token.STRING) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
}
case Token.NE:
if (outcome) {
return caseEquality
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>()) {
case Token.FUNCTION:
// this handles functions that are assigned to variables or
// properties
// e.g. goog.string.htmlEscape = function(str) {
// }
// get the function name and see if it's empty
Node functionNameNode = n.getFirstChild();
String functionName = functionNameNode.getString();
if (functionName.length() == 0) {
if (parent.getType() == Token.ASSIGN) {
// this is an assignment to a property, generally either a
// static function or a prototype function
// e.g. goog.string.htmlEscape = function() { } or
// goog.structs.Map.prototype.getCount = function() { }
Node lhs = parent.getFirstChild();
String name = namer.getName(lhs);
namer.setFunctionName(name, n);
} else if (parent.getType() == Token.NAME) {
// this is an assignment to a variable
// e.g. var handler = function() {}
String name = namer.getName(parent);
namer.setFunctionName(name, n);
}
}
break;
case Token.ASSIGN:
// this handles functions that are assigned to a prototype through
// an object literal
// e.g. BuzzApp.prototype = {
// Start : function() { }
// }
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (rhs.getType() == Token.OBJECTLIT) {
nameObjectLiteralMethods(rhs, namer.getName(lhs));
}
}
}
private void nameObjectLiteralMethods(Node objectLiteral, String context) {
// Object literals are a list of key-value pairs. All object
// literals produced by the parser have an even number of
// children.
Preconditions.checkState(objectLiteral.getChildCount() % 2 == 0);
for (Node keyNode = objectLiteral.getFirstChild();
keyNode != null;
keyNode = keyNode.getNext().getNext()) { // skip 2 for next key
Node valueNode = keyNode.getNext();
// Object literal keys may be strings or numbers. Numbers are
// skipped because name tokens may not start with a number.
if (keyNode.getType() == Token
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.STRING) {
// concatenate the context and key name to get a new qualified name.
String name = namer.getCombinedName(context, namer.getName(keyNode));
int type = valueNode.getType();
if (type == Token.FUNCTION) {
// set name if function is anonymous
Node functionNameNode = valueNode.getFirstChild();
String functionName = functionNameNode.getString();
if (functionName.isEmpty()) {
namer.setFunctionName(name, valueNode);
}
} else if (type == Token.OBJECTLIT) {
// process nested object literal
nameObjectLiteralMethods(valueNode, name);
}
}
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.Node;
import java.util.Map;
/**
* Memoize a scope creator.
*
* This allows you to make multiple passes, without worrying about
* the expense of generating Scope objects over and over again.
*
* On the other hand, you also have to be more aware of what your passes
* are doing. Scopes are memoized stupidly, so if the underlying tree
* changes, the scope may be out of sync.
*
* @author nicksantos@google.com (Nick Santos)
*/
class MemoizedScopeCreator implements ScopeCreator {
private final Map<Node, Scope> scopes = Maps.newHashMap();
private final ScopeCreator delegate;
/**
* @param delegate The real source of Scope objects.
*/
MemoizedScopeCreator(ScopeCreator delegate) {
this.delegate = delegate;
}
@Override
public Scope createScope(Node n, Scope parent) {
Scope scope = scopes.get(n);
if (scope == null) {
scope = delegate.createScope(n, parent);
scopes.put(n, scope);
} else {
Preconditions.checkState(parent == scope.getParent());
}
return scope;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>edGraphNode<N, E> src = (LinkedDirectedGraphNode<N, E>) node;
node = getDirectedGraphNode(destValue);
if (node == null) {
throw new IllegalArgumentException(
destValue + " does not exist in graph");
}
LinkedDirectedGraphNode<N, E> dest = (LinkedDirectedGraphNode<N, E>) node;
LinkedDirectedGraphEdge<N, E> edge =
new LinkedDirectedGraphEdge<N, E>(src, edgeValue, dest);
src.getOutEdges().add(edge);
dest.getInEdges().add(edge);
return edge;
}
@Override
public void disconnect(N n1, N n2) {
disconnectInDirection(n1, n2);
disconnectInDirection(n2, n1);
}
@Override
public void disconnectInDirection(N srcValue, N destValue) {
DiGraphNode<N, E> node = getDirectedGraphNode(srcValue);
if (node == null) {
throw new IllegalArgumentException(
srcValue + " does not exist in graph");
}
LinkedDirectedGraphNode<N, E> src = (LinkedDirectedGraphNode<N, E>) node;
node = getDirectedGraphNode(destValue);
if (node == null) {
throw new IllegalArgumentException(
destValue + " does not exist in graph");
}
LinkedDirectedGraphNode<N, E> dest = (LinkedDirectedGraphNode<N, E>) node;
for (DiGraphEdge<?, E> edge : getDirectedGraphEdges(srcValue, destValue)) {
src.getOutEdges().remove(edge);
dest.getInEdges().remove(edge);
}
}
@Override
public List<DiGraphNode<N, E>> getDirectedGraphNodes() {
List<DiGraphNode<N, E>> nodeList = Lists.newArrayList();
nodeList.addAll(nodes.values());
return nodeList;
}
@Override
public DiGraphNode<N, E> getDirectedGraphNode(N nodeValue) {
return nodes.get(nodeValue);
}
@Override
public GraphNode<N, E> getNode(N nodeValue) {
return getDirectedGraphNode
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(nodeValue);
}
@Override
public List<DiGraphEdge<N, E>> getInEdges(N nodeValue) {
LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue);
if (node == null) {
throw new IllegalArgumentException(
nodeValue + " does not exist in graph");
}
List<DiGraphEdge<N, E>> edgeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : node.getInEdges()) {
edgeList.add(edge);
}
return edgeList;
}
@Override
public List<DiGraphEdge<N, E>> getOutEdges(N nodeValue) {
LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue);
if (node == null) {
throw new IllegalArgumentException(
nodeValue + " does not exist in graph");
}
List<DiGraphEdge<N, E>> edgeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : node.getOutEdges()) {
edgeList.add(edge);
}
return edgeList;
}
@Override
public DiGraphNode<N, E> createDirectedGraphNode(N nodeValue) {
LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue);
if (node == null) {
node = new LinkedDirectedGraphNode<N, E>(nodeValue);
nodes.put(nodeValue, node);
}
return node;
}
@Override
public List<GraphEdge<N, E>> getEdges(N n1, N n2) {
// Since this is a method from a generic graph, edges from both
// directions must be added to the returning list.
List<DiGraphEdge<N, E>> forwardEdges = getDirectedGraphEdges(n1, n2);
List<DiGraphEdge<N, E>> backwardEdges = getDirectedGraphEdges(n2, n1);
int totalSize = forwardEdges.size() + backwardEdges.size();
List<GraphEdge<N, E>> edges = Lists.newArrayListWithCapacity(totalSize);
edges.addAll(forwardEdges);
edges.addAll(backwardEdges);
return edges;
}
@Override
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS><N, E> dNode) {
if (dNode == null) {
throw new IllegalArgumentException(dNode + " is null");
}
List<DiGraphNode<N, E>> nodeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : dNode.getInEdges()) {
nodeList.add(edge.getSource());
}
return nodeList;
}
@Override
public List<DiGraphNode<N, E>> getDirectedSuccNodes(
DiGraphNode<N, E> dNode) {
if (dNode == null) {
throw new IllegalArgumentException(dNode + " is null");
}
List<DiGraphNode<N, E>> nodeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : dNode.getOutEdges()) {
nodeList.add(edge.getDestination());
}
return nodeList;
}
@Override
public boolean isConnected(N n1, N n2) {
return isConnectedInDirection(n1, n2) || isConnectedInDirection(n2, n1);
}
@Override
public List<GraphvizEdge> getGraphvizEdges() {
List<GraphvizEdge> edgeList = Lists.newArrayList();
for (LinkedDirectedGraphNode<N, E> node : nodes.values()) {
for (DiGraphEdge<N, E> edge : node.getOutEdges()) {
edgeList.add((LinkedDirectedGraphEdge<N, E>) edge);
}
}
return edgeList;
}
@Override
public List<GraphvizNode> getGraphvizNodes() {
List<GraphvizNode> nodeList =
Lists.newArrayListWithCapacity(nodes.size());
for (LinkedDirectedGraphNode<N, E> node : nodes.values()) {
nodeList.add(node);
}
return nodeList;
}
@Override
public String getName() {
return "LinkedGraph";
}
@Override
public boolean isDirected() {
return true;
}
@Override
public List<GraphNode<N, E>> getNodes() {
List<GraphNode<N, E>> list = Lists.newArrayList();
list.addAll(nodes.values());
return list;
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
@Override
public List<GraphNode<N, E>> getNeighborNodes(N value) {
DiGraphNode<N, E> node = getDirectedGraphNode(value);
return getNeighborNodes(node);
}
public List<GraphNode<N, E>> getNeighborNodes(DiGraphNode<N, E> node) {
List<GraphNode<N, E>> result = Lists.newArrayList();
for (Iterator<GraphNode<N, E>> i =
((LinkedDirectedGraphNode<N, E>) node).neighborIterator();i.hasNext();) {
result.add(i.next());
}
return result;
}
@Override
public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) {
LinkedDirectedGraphNode<N, E> node = nodes.get(value);
Preconditions.checkNotNull(node);
return node.neighborIterator();
}
@Override
public List<GraphEdge<N, E>> getEdges() {
List<GraphEdge<N, E>> result = Lists.newArrayList();
for (DiGraphNode<N, E> node : nodes.values()) {
for (DiGraphEdge<N, E> edge : node.getOutEdges()) {
result.add(edge);
}
}
return result;
}
@Override
public int getNodeDegree(N value) {
DiGraphNode<N, E> node = getDirectedGraphNode(value);
if (node == null) {
throw new IllegalArgumentException(value + " not found in graph");
}
return node.getInEdges().size() + node.getOutEdges().size();
}
/**
* A directed graph node that stores outgoing edges and incoming edges as an
* list within the node itself.
*/
static class LinkedDirectedGraphNode<N, E> implements DiGraphNode<N, E>,
GraphvizNode {
protected List<DiGraphEdge<N, E>> inEdgeList = Lists.newArrayList();
protected List<DiGraphEdge<N, E>> outEdgeList =
Lists.newArrayList();
protected final N value;
protected Annotation annotation;
protected int id;
private static int totalNodes = 0;
/**
* Constructor
*
* @param nodeValue Node's value.
*/
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> * "directly" inlined functions must meet these additional requirements:
* - consists of a single return statement
*
*
* @author johnlenz@google.com (John Lenz)
*/
class InlineFunctions implements CompilerPass {
// TODO(nicksantos): This needs to be completely rewritten to use scopes
// to do variable lookups. Right now, it assumes that all functions are
// uniquely named variables. There's currently a stopgap scope-check
// to ensure that this doesn't produce invalid code. But in the long run,
// this needs a major refactor.
private final Map<String, FunctionState> fns = Maps.newHashMap();
private final Map<Node, String> anonFns = Maps.newHashMap();
private final AbstractCompiler compiler;
private final FunctionInjector injector;
private final boolean blockFunctionInliningEnabled;
private final boolean inlineAnonymousFunctionExpressions;
private final boolean inlineGlobalFunctions;
private final boolean inlineLocalFunctions;
InlineFunctions(AbstractCompiler compiler,
Supplier<String> safeNameIdSupplier,
boolean inlineGlobalFunctions,
boolean inlineLocalFunctions,
boolean inlineAnonymousFunctionExpressions,
boolean blockFunctionInliningEnabled,
boolean enableExpressionDecomposition) {
Preconditions.checkArgument(compiler != null);
Preconditions.checkArgument(safeNameIdSupplier != null);
this.compiler = compiler;
this.inlineGlobalFunctions = inlineGlobalFunctions;
this.inlineLocalFunctions = inlineLocalFunctions;
this.inlineAnonymousFunctionExpressions =
inlineAnonymousFunctionExpressions;
this.blockFunctionInliningEnabled = blockFunctionInliningEnabled;
this.injector = new FunctionInjector(
compiler, safeNameIdSupplier, enableExpressionDecomposition);
}
FunctionState getOrCreateFunctionState(String fnName) {
FunctionState fs = fns.get(fnName);
if (fs == null) {
fs = new FunctionState();
fns.put(fnName, fs);
}
return fs;
}
/**
* {@inheritDoc}
*/
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.isNormalized());
NodeTraversal.traverse(compiler, root, new FindCandidateFunctions());
if (fns.isEmpty()) {
return; // Nothing left to do.
}
NodeTraversal.traverse(compiler, root,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>): Make this a Preconditions check.
// Currently this fails for some targets.
if (n.hasOneChild()) {
// Only look at declarations in the global scope.
Node nameNode = n.getFirstChild();
if (nameNode.getType() == Token.NAME && nameNode.hasChildren()
&& nameNode.getFirstChild().getType() == Token.FUNCTION) {
maybeAddFunction(new FunctionVar(n), t.getModule());
}
}
break;
// Named functions
// function Foo(x) { return ... }
case Token.FUNCTION:
Preconditions.checkState(NodeUtil.isStatementBlock(parent)
|| parent.getType() == Token.LABEL);
Function fn = new NamedFunction(n);
String name = fn.getName();
if (!name.isEmpty()) {
maybeAddFunction(fn, t.getModule());
}
break;
}
}
/**
* Find anonymous functions that are called directly in the form of
* (function(a,b,...){...})(a,b,...)
* or
* (function(a,b,...){...}).call(this,a,b, ...)
*/
public void findAnonymousFunctionExpressions(NodeTraversal t, Node n) {
switch (n.getType()) {
// Anonymous functions in the form of:
// (function(){})();
case Token.CALL:
Node fnNode = null;
if (n.getFirstChild().getType() == Token.FUNCTION) {
fnNode = n.getFirstChild();
} else if (NodeUtil.isFunctionObjectCall(n)) {
Node fnIdentifingNode = n.getFirstChild().getFirstChild();
if (fnIdentifingNode.getType() == Token.FUNCTION) {
fnNode = fnIdentifingNode;
}
}
// If a interesting function was discovered, add it.
if (fnNode != null) {
Function fn = new AnonymousFunction(fnNode, callsSeen++);
maybeAddFunction(fn, t.getModule());
anonFns.put(fnNode, fn.getName());
}
break;
}
}
}
/**
* Updates the FunctionState object for the given function. Checks if the
* given function matches the criteria for an inlinable function.
*/
private void maybeAddFunction
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> name can be replaced externally, any inlined instance
// would not reflect this change.
// To allow inlining we need to be able to distinguish between exports
// that are used in a read-only fashion and those that can be replaced
// by external definitions.
return false;
}
// Don't inline this special function
if (RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(fnName)) {
return false;
}
Node fnNode = fn.getFunctionNode();
return injector.doesFunctionMeetMinimumRequirements(fnName, fnNode);
}
/**
* @see CallVisitor
*/
private interface CallVisitorCallback {
public void visitCallSite(
NodeTraversal t, Node callNode, Node parent, FunctionState fs);
}
/**
* Visit call sites for functions in functionMap.
*/
private static class CallVisitor extends AbstractPostOrderCallback {
protected CallVisitorCallback callback;
private Map<String, FunctionState> functionMap;
private Map<Node, String> anonFunctionMap;
CallVisitor(Map<String, FunctionState> fns,
Map<Node, String> anonFns,
CallVisitorCallback callback) {
this.functionMap = fns;
this.anonFunctionMap = anonFns;
this.callback = callback;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
// Function calls
case Token.CALL:
Node child = n.getFirstChild();
String name = null;
// NOTE: The normalization pass insures that local names do not
// collide with global names.
if (child.getType() == Token.NAME) {
name = child.getString();
} else if (child.getType() == Token.FUNCTION) {
name = anonFunctionMap.get(child);
} else if (NodeUtil.isFunctionObjectCall(n)) {
Preconditions.checkState(NodeUtil.isGet(child));
Node fnIdentifingNode = child.getFirstChild();
if (fnIdentifingNode.getType() == Token.NAME) {
name = fnIdentifingNode.getString();
} else if (fnIdentifingNode.getType() == Token.FUNCTION) {
name = anonFunctionMap.get(fnIdent
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>canInlineReferenceToFunction(
t, callNode, fs.getFn().getFunctionNode(),
fs.getNamesToAlias(), mode, fs.getReferencesThis());
if (result != CanInlineResult.NO) {
// Yeah!
boolean decompose =
(result == CanInlineResult.AFTER_DECOMPOSITION);
fs.addReference(new Reference(callNode, module, mode, decompose));
return true;
}
return false;
}
/**
* Find functions that can be inlined.
*/
private void checkNameUsage(NodeTraversal t, Node n, Node parent) {
Preconditions.checkState(n.getType() == Token.NAME);
if (parent.getType() == Token.VAR || parent.getType() == Token.FUNCTION) {
// This is a declaration. Duplicate declarations are handle during
// function candidate gathering.
return;
}
if (parent.getType() == Token.CALL && parent.getFirstChild() == n) {
// This is a normal reference to the function.
return;
}
// Check for a ".call" to the named function:
// CALL
// GETPROP/GETELEM
// NAME
// STRING == "call"
// This-Value
// Function-parameter-1
// ...
if (NodeUtil.isGet(parent)
&& n == parent.getFirstChild()
&& n.getNext().getType() == Token.STRING
&& n.getNext().getString().equals("call")) {
Node gramps = n.getAncestor(2);
if (gramps.getType() == Token.CALL
&& gramps.getFirstChild() == parent) {
// Yep, a ".call".
return;
}
}
// Other refs to a function name remove its candidacy for inlining
String name = n.getString();
FunctionState fs = fns.get(name);
if (fs == null) {
return;
}
// If the name is being assigned to it can not be inlined.
if (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) {
// e.g. bar = something; <== we can't inline "bar"
// so mark the function as uninlinable.
//
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> TODO(johnlenz): Should we just remove it from fns here?
fs.setInline(false);
} else {
// e.g. var fn = bar; <== we can't inline "bar"
// As this reference can't be inlined mark the function as
// unremovable.
fs.setRemove(false);
}
}
}
/**
* Inline functions at the call sites.
*/
private static class Inline implements CallVisitorCallback {
private final FunctionInjector injector;
Inline(FunctionInjector injector) {
this.injector = injector;
}
public void visitCallSite(
NodeTraversal t, Node callNode, Node parent, FunctionState fs) {
Preconditions.checkState(fs.hasExistingFunctionDefinition());
if (fs.canInline()) {
Reference ref = fs.getReference(callNode);
// There are two cases ref can be null: if the call site was introduce
// because it was part of a function that was inlined during this pass
// or if the call site was trimmed from the list of references because
// the function couldn't be inlined at this location.
if (ref != null) {
inlineFunction(t, callNode, fs, ref.mode);
// Keep track of references that have been inlined so that
// we can verify that none have been missed.
ref.inlined = true;
}
}
}
/**
* Inline a function into the call site.
*/
private void inlineFunction(
NodeTraversal t, Node callNode, FunctionState fs, InliningMode mode) {
Function fn = fs.getFn();
String fnName = fn.getName();
Node fnNode = fs.getSafeFnNode();
Node newCode = injector.inline(t, callNode, fnName, fnNode, mode);
t.getCompiler().reportCodeChange();
t.getCompiler().addToDebugLog("Inlined function: " + fn.getName());
}
}
/**
* Remove entries that aren't a valid inline candidates, from the list of
* encountered names.
*/
private void trimCanidatesNotMeetingMinimumRequirements() {
Iterator<Entry<String, FunctionState>> i;
for (i = fns.entrySet().iterator(); i.hasNext();) {
FunctionState fs = i.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> in the list of function
* references so they won't be inlined (which is what we want). Here we mark
* those functions as non-removable (as they will have new references in the
* cloned node trees).
*
* This prevents a function that would only be inlined because it is
* referenced once from being inlined into multiple call sites because
* the calling function has been inlined in multiple locations or the
* function being removed while there are still references.
*/
private void resolveInlineConflicts() {
for (FunctionState fs : fns.values()) {
resolveInlineConflictsForFunction(fs);
}
}
/**
* @see #resolveInlineConflicts
*/
private void resolveInlineConflictsForFunction(FunctionState fs) {
// Functions that aren't referenced don't cause conflicts.
if (!fs.hasReferences()) {
return;
}
Node fnNode = fs.getFn().getFunctionNode();
Set<String> names = findCalledFunctions(fnNode);
if (!names.isEmpty()) {
// Prevent the removal of the referenced functions.
for (String name : names) {
FunctionState fsCalled = fns.get(name);
if (fsCalled != null && fsCalled.canRemove()) {
fsCalled.setRemove(false);
// For functions that can no longer be removed, check if they should
// still be inlined.
if (!mimimizeCost(fsCalled)) {
// It can't be inlined remove it from the list.
fsCalled.setInline(false);
}
}
}
// Make a copy of the Node, so it isn't changed by other inlines.
fs.setSafeFnNode(fs.getFn().getFunctionNode().cloneTree());
}
}
/**
* This functions that may be called directly.
*/
private Set<String> findCalledFunctions(Node node) {
Set<String> changed = Sets.newHashSet();
findCalledFunctions(node, changed);
return changed;
}
/**
* @see #findCalledFunctions(Node)
*/
private void findCalledFunctions(
Node node, Set<String> changed) {
Preconditions.checkArgument(changed != null);
// For each referenced function, add a new reference
if (node.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>getType() == Token.CALL) {
Node child = node.getFirstChild();
if (child.getType() == Token.NAME) {
String name = child.getString();
changed.add(name);
}
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
findCalledFunctions(c, changed);
}
}
/**
* For any call-site that needs it, prepare the call-site for inlining
* by rewriting the containing expression.
*/
private void decomposeExpressions(Set<String> fnNames) {
ExpressionDecomposer decomposer = new ExpressionDecomposer(
compiler, compiler.getUniqueNameIdSupplier(), fnNames);
for (FunctionState fs : fns.values()) {
if (fs.canInline()) {
for (Reference ref : fs.getReferences()) {
if (ref.requiresDecomposition) {
decomposer.maybeDecomposeExpression(ref.callNode);
}
}
}
}
}
/**
* Removed inlined functions that no longer have any references.
*/
void removeInlinedFunctions() {
for (FunctionState fs : fns.values()) {
if (fs.canRemove()) {
Function fn = fs.getFn();
Preconditions.checkState(fs.canInline());
Preconditions.checkState(fn != null);
verifyAllReferencesInlined(fs);
fn.remove();
compiler.reportCodeChange();
}
}
}
/**
* Sanity check to verify, that expression rewriting didn't
* make a call inaccessible.
*/
void verifyAllReferencesInlined(FunctionState fs) {
for (Reference ref : fs.getReferences()) {
if (!ref.inlined) {
throw new IllegalStateException("Call site missed.");
}
}
}
/**
* Use to track the decisions that have been make about a function.
*/
private static class FunctionState {
private Function fn = null;
private Node safeFnNode = null;
private boolean inline = true;
private boolean remove = true;
private boolean inlineDirectly = false;
private boolean referencesThis = false;
private Map<Node, Reference> references = null;
private JSModule module = null;
private Set<String> namesToAlias = null;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
boolean hasExistingFunctionDefinition() {
return (fn != null);
}
public void setReferencesThis(boolean referencesThis) {
this.referencesThis = referencesThis;
}
public boolean getReferencesThis() {
return this.referencesThis;
}
void removeBlockInliningReferences() {
Iterator<Entry<Node, Reference>> i;
for (i = getReferencesInternal().entrySet().iterator(); i.hasNext();) {
Entry<Node, Reference> entry = i.next();
if (entry.getValue().mode == InliningMode.BLOCK) {
i.remove();
}
}
}
public boolean hasBlockInliningReferences() {
for (Reference r : getReferencesInternal().values()) {
if (r.mode == InliningMode.BLOCK) {
return true;
}
}
return false;
}
public Function getFn() {
return fn;
}
public void setFn(Function fn) {
Preconditions.checkState(this.fn == null);
this.fn = fn;
}
public Node getSafeFnNode() {
return (safeFnNode != null) ? safeFnNode : fn.getFunctionNode();
}
public void setSafeFnNode(Node safeFnNode) {
this.safeFnNode = safeFnNode;
}
public boolean canInline() {
return inline;
}
public void setInline(boolean inline) {
this.inline = inline;
if (inline == false) {
// No need to keep references to function that can't be inlined.
references = null;
// Don't remove functions that we aren't inlining.
remove = false;
}
}
public boolean canRemove() {
return remove;
}
public void setRemove(boolean remove) {
this.remove = remove;
}
public boolean canInlineDirectly() {
return inlineDirectly;
}
public void inlineDirectly(boolean directReplacement) {
this.inlineDirectly = directReplacement;
}
public boolean hasReferences() {
return (references != null && !references.isEmpty());
}
private Map<Node, Reference> getReferencesInternal() {
if (references == null) {
return Collections.emptyMap();
}
return references;
}
public void addReference(Reference ref) {
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.ExpressionDecomposer.DecompositionType;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* A set of utility functions that replaces CALL with a specified
* FUNCTION body, replacing and aliasing function parameters as
* necessary.
*
* @author johnlenz@google.com (John Lenz)
*/
class FunctionInjector {
private final AbstractCompiler compiler;
private final Supplier<String> safeNameIdSupplier;
private final boolean allowDecomposition;
private Set<String> knownConstants = Sets.newHashSet();
/**
* @param allowDecomposition Whether an effort should be made to break down
* expressions into simpler expressions to allow functions to be injected
* where they would otherwise be disallowed.
*/
public FunctionInjector(
AbstractCompiler compiler,
Supplier<String> safeNameIdSupplier,
boolean allowDecomposition) {
Preconditions.checkNotNull(compiler);
Preconditions.checkNotNull(safeNameIdSupplier);
this.compiler = compiler;
this.safeNameIdSupplier = safeNameIdSupplier;
this.allowDecomposition = allowDecomposition;
}
/** The type of inlining to perform. */
enum InliningMode {
/**
* Directly replace the call expression. Only functions
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>isFunctionObjectApply(callNode)) {
return false;
}
}
return true;
}
/**
* Inline a function into the call site.
*/
Node inline(
NodeTraversal t, Node callNode, String fnName, Node fnNode,
InliningMode mode) {
Preconditions.checkState(compiler.isNormalized());
if (mode == InliningMode.DIRECT) {
return inlineReturnValue(callNode, fnNode);
} else {
return inlineFunction(callNode, fnNode, fnName);
}
}
/**
* Inline a function that fulfills the requirements of
* canInlineReferenceDirectly into the call site, replacing only the CALL
* node.
*/
private Node inlineReturnValue(Node callNode, Node fnNode) {
Node block = fnNode.getLastChild();
Node callParentNode = callNode.getParent();
// NOTE: As the normalize pass guarantees globals aren't being
// shadowed and an expression can't introduce new names, there is
// no need to check for conflicts.
// Create an argName -> expression map, checking for side effects.
Map<String, Node> argMap =
FunctionArgumentInjector.getFunctionCallParameterMap(
fnNode, callNode, this.safeNameIdSupplier);
Node newExpression;
if (!block.hasChildren()) {
newExpression = NodeUtil.newUndefinedNode();
} else {
Node returnNode = block.getFirstChild();
Preconditions.checkArgument(returnNode.getType() == Token.RETURN);
// Clone the return node first.
Node safeReturnNode = returnNode.cloneTree();
Node inlineResult = FunctionArgumentInjector.inject(
safeReturnNode, null, argMap);
Preconditions.checkArgument(safeReturnNode == inlineResult);
newExpression = safeReturnNode.removeFirstChild();
}
callParentNode.replaceChild(callNode, newExpression);
return newExpression;
}
/**
* Supported call site types.
*/
private enum CallSiteType {
/**
* Used for a call site for which there does not exist a method
* to inline it.
*/
UNSUPPORTED,
/**
* A call as a statement. For example: "foo();".
* EXPR_RESULT
* CALL
*/
SIMPLE_CALL,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> !NodeUtil.isConstantName(parent)
&& grandParent.getType() == Token.VAR
&& grandParent.hasOneChild()) {
// This is a var declaration. Example: "var x = foo();"
// TODO(johnlenz): Should we be checking for constants on the
// left-hand-side of the assignments (and handling them as EXPRESSION?
return CallSiteType.VAR_DECL_SIMPLE_ASSIGNMENT;
} else {
Node expressionRoot = ExpressionDecomposer.findExpressionRoot(callNode);
if (expressionRoot != null) {
ExpressionDecomposer decomposer = new ExpressionDecomposer(
compiler, safeNameIdSupplier, knownConstants);
DecompositionType type = decomposer.canExposeExpression(
callNode);
if (type == DecompositionType.MOVABLE) {
return CallSiteType.EXPRESSION;
} else if (type == DecompositionType.DECOMPOSABLE) {
return CallSiteType.DECOMPOSABLE_EXPRESSION;
} else {
Preconditions.checkState(type == DecompositionType.UNDECOMPOSABLE);
}
}
}
return CallSiteType.UNSUPPORTED;
}
/**
* Inline a function which fulfills the requirements of
* canInlineReferenceAsStatementBlock into the call site, replacing the
* parent expression.
*/
private Node inlineFunction(
Node callNode, Node fnNode, String fnName) {
Node parent = callNode.getParent();
Node grandParent = parent.getParent();
// TODO(johnlenz): Consider storing the callSite classification in the
// reference object and passing it in here.
CallSiteType callSiteType = classifyCallSite(callNode);
Preconditions.checkArgument(callSiteType != CallSiteType.UNSUPPORTED);
// Store the name for the result. This will be used to
// replace "return expr" statements with "resultName = expr"
// to replace
String resultName = null;
boolean needsDefaultReturnResult = true;
switch (callSiteType) {
case SIMPLE_ASSIGNMENT:
resultName = parent.getFirstChild().getString();
break;
case VAR_DECL_SIMPLE_ASSIGNMENT:
resultName = parent.getString();
break;
case SIMPLE_CALL:
resultName = null; // "foo()" doesn't need
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> a result.
needsDefaultReturnResult = false;
break;
case EXPRESSION:
resultName = getUniqueResultName();
needsDefaultReturnResult = false; // The intermediary result already
// has the default value.
break;
case DECOMPOSABLE_EXPRESSION:
throw new IllegalStateException(
"Decomposable expressions must decomposed before inlining.");
default:
throw new IllegalStateException("Unexpected call site type.");
}
boolean isCallInLoop = isCallWithinLoop(callNode);
FunctionToBlockMutator mutator = new FunctionToBlockMutator(
compiler, this.safeNameIdSupplier);
Node newBlock = mutator.mutate(
fnName, fnNode, callNode, resultName,
needsDefaultReturnResult, isCallInLoop);
// TODO(nicksantos): Create a common mutation function that
// can replace either a VAR name assignment, assignment expression or
// a EXPR_RESULT.
Node greatGrandParent = grandParent.getParent();
switch (callSiteType) {
case VAR_DECL_SIMPLE_ASSIGNMENT:
// Remove the call from the name node.
parent.removeChild(parent.getFirstChild());
Preconditions.checkState(parent.getFirstChild() == null);
// Add the call, after the VAR.
greatGrandParent.addChildAfter(newBlock, grandParent);
break;
case SIMPLE_ASSIGNMENT:
// The assignment is now part of the inline function so
// replace it completely.
Preconditions.checkState(NodeUtil.isExpressionNode(grandParent));
greatGrandParent.replaceChild(grandParent, newBlock);
break;
case SIMPLE_CALL:
// If nothing is looking at the result just replace the call.
Preconditions.checkState(NodeUtil.isExpressionNode(parent));
grandParent.replaceChild(parent, newBlock);
break;
case EXPRESSION:
// TODO(johnlenz): Maybe change this so that movable and decomposable
// expressions are handled the same way: The call is moved and
// then handled by one the three basic cases, rather than
// introducing a new case.
Node injectionPoint = ExpressionDecomposer.findInjectionPoint(callNode);
Preconditions.checkNotNull(injectionPoint);
Node injectionPointParent = injectionPoint.getParent();
Preconditions.checkNotNull(injection
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>PointParent);
Preconditions.checkState(
NodeUtil.isStatementBlock(injectionPointParent));
// Declare the intermediate result name.
newBlock.addChildrenToFront(NodeUtil.newVarNode(resultName, null));
// Inline the function before the selected injection point (before
// the call).
injectionPointParent.addChildBefore(newBlock, injectionPoint);
// Replace the call site with a reference to the intermediate
// result name.
parent.replaceChild(callNode, Node.newString(Token.NAME, resultName));
break;
default:
throw new IllegalStateException("Unexpected call site type.");
}
return newBlock;
}
/**
* @return Whether the specified callNode has a loop parent that
* is within the current scope.
*/
private boolean isCallWithinLoop(Node callNode) {
for (Node parent : callNode.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (NodeUtil.isFunction(parent)) {
break;
}
}
return false;
}
/**
* Checks if the given function matches the criteria for an inlinable
* function, and if so, adds it to our set of inlinable functions.
*/
boolean isDirectCallNodeReplacementPossible(Node fnNode) {
// Only inline single-statement functions
Node block = NodeUtil.getFunctionBody(fnNode);
// Check if this function is suitable for direct replacement of a CALL node:
// a function that consists of single return that returns an expression.
if (!block.hasChildren()) {
// special case empty functions.
return true;
} else if (block.hasOneChild()) {
// Only inline functions that return something.
if (block.getFirstChild().getType() == Token.RETURN
&& block.getFirstChild().getFirstChild() != null) {
return true;
}
}
return false;
}
enum CanInlineResult {
YES,
AFTER_DECOMPOSITION,
NO
}
/**
* Determines whether a function can be inlined at a particular call site.
* There are several criteria that the function and reference must hold in
* order for the functions to be inlined:
* - It must be a simple call, or assignment, or var initialization.
*
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> we aren't adding any
// additional VAR declarations because aliasing is needed.
if (callerContainsFunction) {
Map<String, Node> args =
FunctionArgumentInjector.getFunctionCallParameterMap(
fnNode, callNode, this.safeNameIdSupplier);
boolean hasArgs = !args.isEmpty();
if (hasArgs) {
// Limit the inlining
Set<String> allNamesToAlias = Sets.newHashSet(namesToAlias);
FunctionArgumentInjector.maybeAddTempsForCallArguments(
fnNode, args, allNamesToAlias, compiler.getCodingConvention());
if (!allNamesToAlias.isEmpty()) {
return false;
}
}
}
return true;
}
/**
* Determines whether a function can be inlined at a particular call site.
* There are several criteria that the function and reference must hold in
* order for the functions to be inlined:
* 1) If a call's arguments have side effects,
* the corresponding argument in the function must only be referenced once.
* For instance, this will not be inlined:
* <pre>
* function foo(a) { return a + a }
* x = foo(i++);
* </pre>
*/
private CanInlineResult canInlineReferenceDirectly(
Node callNode, Node fnNode) {
if (!isDirectCallNodeReplacementPossible(fnNode)) {
return CanInlineResult.NO;
}
Node block = fnNode.getLastChild();
// CALL NODE: [ NAME, ARG1, ARG2, ... ]
Node cArg = callNode.getFirstChild().getNext();
// Functions called via 'call' and 'apply' have a this-object as
// the first parameter, but this is not part of the called function's
// parameter list.
if (callNode.getFirstChild().getType() != Token.NAME) {
if (NodeUtil.isFunctionObjectCall(callNode)) {
// TODO(johnlenz): Support replace this with a value.
Preconditions.checkNotNull(cArg);
Preconditions.checkState(cArg.getType() == Token.THIS);
cArg = cArg.getNext();
} else {
// ".apply" call should be filtered before this.
Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> is zero for empty functions.
return -costDeltaFunctionOverhead;
}
if (mode == InliningMode.DIRECT) {
// The part of the function that is inlined using direct inlining:
// "return " (7)
return -(costDeltaFunctionOverhead + 7);
} else {
int aliasCount = namesToAlias.size();
// Originally, we estimated purely base on the function code size, relying
// on later optimizations. But that did not produce good results, so here
// we try to estimate the something closer to the actual inlined coded.
// NOTE 1: Result overhead is only if there is an assignment, but
// getting that information would require some refactoring.
// NOTE 2: The aliasing overhead is currently an under-estimate,
// as some parameters are aliased because of the parameters used.
// Perhaps we should just assume all parameters will be aliased?
final int INLINE_BLOCK_OVERHEAD = 4; // "X:{}"
final int PER_RETURN_OVERHEAD = 2; // "return" --> "break X"
final int PER_RETURN_RESULT_OVERHEAD = 3; // "XX="
final int PER_ALIAS_OVERHEAD = 3; // "XX="
// TODO(johnlenz): Counting the number of returns is relatively expensive
// this information should be determined during the traversal and
// cached.
int returnCount = NodeUtil.getNodeTypeReferenceCount(block, Token.RETURN);
int resultCount = (returnCount > 0) ? returnCount - 1 : 0;
int baseOverhead = (returnCount > 0) ? INLINE_BLOCK_OVERHEAD : 0;
int overhead = baseOverhead
+ returnCount * PER_RETURN_OVERHEAD
+ resultCount * PER_RETURN_RESULT_OVERHEAD
+ aliasCount * PER_ALIAS_OVERHEAD;
return (overhead - costDeltaFunctionOverhead);
}
}
/**
* Store the names of known constants to be used when classifying call-sites
* in expressions.
*/
public void setKnownConstants(Set<String> knownConstants) {
// This is only expected to be set once. The same set should be used
// when evaluating call-sites and inlining calls.
Preconditions
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.checkState(this.knownConstants.isEmpty());
this.knownConstants = knownConstants;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Prepare the AST before we do any checks or optimizations on it.
*
* This pass must run. It should bring the AST into a consistent state,
* and add annotations where necessary. It should not make any transformations
* on the tree that would lose source information, since we need that source
* information for checks.
*
* @author johnlenz@google.com (John Lenz)
*/
class PrepareAst implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean assertOnChange;
PrepareAst(AbstractCompiler compiler) {
this(compiler, false);
}
PrepareAst(AbstractCompiler compiler, boolean forbidChanges) {
this.compiler = compiler;
this.assertOnChange = forbidChanges;
}
private void reportChange() {
if (assertOnChange) {
Preconditions.checkState(false, "normalizeNodeType constraints violated");
}
}
@Override
public void process(Node externs, Node root) {
normalizeNodeTypes(root);
if (externs != null) {
NodeTraversal.traverse(
compiler, externs, new PrepareAnnotations(compiler));
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations(compiler));
}
}
/**
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> * Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
if (n.getType() == Token.EXPR_VOID) {
n.setType(Token.EXPR_RESULT);
reportChange();
}
// Remove unused properties to minimize differences between ASTs
// produced by the two parsers.
if (n.getType() == Token.FUNCTION) {
Preconditions.checkState(n.getProp(Node.FUNCTION_PROP) == null);
}
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& n.getType() != Token.LABEL
&& n.getType() != Token.SWITCH) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
c.getType() != Token.BLOCK) {
Node newBlock = new Node(Token.BLOCK);
newBlock.copyInformationFrom(n);
n.replaceChild(c, newBlock);
if (c.getType() != Token.EMPTY) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
extends NodeTraversal.AbstractPostOrderCallback {
private final AbstractCompiler compiler;
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.compiler = compiler;
this.convention = compiler.getCodingConvention();
}
/**
*
* In the AST that Rhino gives us, it needs
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
int nType = n.getType();
switch (nType) {
case Token.NAME:
case Token.STRING:
String nString = n.getString();
if (nType == Token.NAME &&
n.getParent().getType() == Token.CALL &&
"eval".equals(nString)) {
n.putBooleanProp(Node.DIRECT_EVAL, true);
}
if (convention.isConstant(nString)) {
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
break;
case Token.FUNCTION:
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null) {
// Look for the info on other nodes.
if (parent.getType() == Token.ASSIGN) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.getType() == Token.NAME) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> != null && typeExpr.isVarArgs()) {
arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true);
}
}
break;
case Token.OBJECTLIT:
if (n.getType() == Token.OBJECTLIT) {
for (Node key = n.getFirstChild();
key != null; key = key.getNext().getNext()) {
Node value = key.getNext();
if (key.getJSDocInfo() != null &&
key.getNext().getType() == Token.FUNCTION) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
}
break;
}
// TODO(johnlenz): Determine if it is possible to simply use the javadoc
// everywhere rather than use IS_DISPATCHER.
/*
* Translate dispatcher info into the property expected node.
*/
if (n.getJSDocInfo() != null && n.getJSDocInfo().isJavaDispatch()) {
if (n.getType() == Token.ASSIGN) {
Node fnNode = n.getLastChild();
Preconditions.checkState(fnNode.getType() == Token.FUNCTION);
fnNode.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Scope and is a
// native type.
if (var.input == null) {
n.setJSType(varType);
if (parent.getType() == Token.VAR) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.getType() == Token.FUNCTION);
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().getType() == Token.EXPR_RESULT) ||
!newType.equals(varType)) {
compiler.report(
JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented.
*/
void expectAllInterfacePropertiesImplemented(FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
if (!instance.hasProperty(prop)) {
Node source = type.getSource();
Preconditions.checkNotNull(source);
String sourceName = (String) source.getProp(Node.SOURCENAME_PROP);
sourceName = sourceName == null ? "" : sourceName;
compiler.report(JSError.make(sourceName, source,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implemented.toString(), instance.toString()));
registerMismatch(instance, implemented);
}
}
}
}
}
/**
* Report a type mismatch
*/
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSType required) {
mismatch(t.getSourceName(), n, msg, found, required);
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.parsing;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.mozilla.rhino.ScriptRuntime;
/**
* This class implements the scanner for JsDoc strings.
*
* It is heavily based on Rhino's TokenStream.
*
*
*/
class JsDocTokenStream {
/*
* For chars - because we need something out-of-range
* to check. (And checking EOF by exception is annoying.)
* Note distinction from EOF token type!
*/
private final static int
EOF_CHAR = -1;
JsDocTokenStream(String sourceString) {
this(sourceString, 0);
}
JsDocTokenStream(String sourceString, int lineno) {
this(sourceString, lineno, 0);
}
JsDocTokenStream(String sourceString, int lineno, int initCharno) {
Preconditions.checkNotNull(sourceString);
this.lineno = lineno;
this.sourceString = sourceString;
this.sourceEnd = sourceString.length();
this.sourceCursor = this.cursor = 0;
this.initLineno = lineno;
this.initCharno = initCharno;
}
/**
* Tokenizes JSDoc comments.
*/
@SuppressWarnings("fallthrough")
final JsDocToken getJsDocToken() {
int c;
stringBufferTop = 0;
for (;;) {
// eat white spaces
for (;;) {
charno = -1;
c = getChar();
if (c == EOF_CHAR) {
return
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.getType() == Token.ASSIGN) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.getType() == Token.NAME);
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.getType() == Token.CALL) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, expecially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element()
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
}
break;
}
// FALL THROUGH
case ON_FALSE:
if (condition == null) {
condition = NodeUtil.getConditionExpression(source);
if (condition == null && source.getType() == Token.CASE) {
condition = source;
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope = traverse(
condition.getFirstChild(), output.createChildFlowScope());
}
}
}
if (condition != null) {
if (condition.getType() == Token.AND ||
condition.getType() == Token.OR) {
// When handling the short-circuiting binary operators,
// the outcome scope on true can be different than the outcome
// scope on false.
//
// TODO(nicksantos): The "right" way to do this is to
// carry the known outcome all the way through the
// recursive traversal, so that we can construct a
// different flow scope based on the outcome. However,
// this would require a bunch of code and a bunch of
// extra computation for an edge case. This seems to be
// a "good enough" approximation.
// conditionOutcomes is cached from previous iterations
// of the loop.
if (conditionOutcomes == null) {
conditionOutcomes = condition.getType() == Token.AND ?
traverseAnd(condition, output.createChildFlowScope()) :
traverseOr(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition,
conditionOutcomes.getOutcomeFlowScope(
condition.getType(), branch == Branch.ON_TRUE),
branch == Branch.ON_TRUE);
} else {
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope =
traverse(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition, conditionFlowScope, branch == Branch.ON_TRUE);
}
}
break;
}
result.add(newScope.optimize());
}
return result;
}
private FlowScope traverse(Node n, FlowScope scope)
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> {
switch (n.getType()) {
case Token.ASSIGN:
scope = traverseAssign(n, scope);
break;
case Token.NAME:
scope = traverseName(n, scope);
break;
case Token.GETPROP:
scope = traverseGetProp(n, scope);
break;
case Token.AND:
scope = traverseAnd(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.OR:
scope = traverseOr(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.HOOK:
scope = traverseHook(n, scope);
break;
case Token.OBJECTLIT:
scope = traverseObjectLiteral(n, scope);
break;
case Token.CALL:
scope = traverseCall(n, scope);
break;
case Token.NEW:
scope = traverseNew(n, scope);
break;
case Token.ASSIGN_ADD:
case Token.ADD:
scope = traverseAdd(n, scope);
break;
case Token.POS:
case Token.NEG:
scope = traverse(n.getFirstChild(), scope); // Find types.
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.ARRAYLIT:
scope = traverseArrayLiteral(n, scope);
break;
case Token.REF_SPECIAL:
n.setJSType(getNativeType(UNKNOWN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.THIS:
n.setJSType(scope.getTypeOfThis());
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> Token.ASSIGN_BITOR:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
case Token.MUL:
case Token.SUB:
case Token.DEC:
case Token.INC:
case Token.BITNOT:
case Token.NUMBER:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.LP:
case Token.GET_REF:
scope = traverse(n.getFirstChild(), scope);
n.setJSType(getJSType(n.getFirstChild()));
break;
case Token.COMMA:
scope = traverseChildren(n, scope);
n.setJSType(getJSType(n.getLastChild()));
break;
case Token.STRING:
case Token.TYPEOF:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
case Token.NOT:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.INSTANCEOF:
case Token.IN:
case Token.TRUE:
case Token.FALSE:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.GETELEM:
scope = traverseGetElem(n, scope);
break;
case Token.EXPR_RESULT:
scope = traverseChildren(n, scope);
if (n.getFirstChild().getType() == Token.GETPROP) {
ensurePropertyDeclared(n.getFirstChild());
}
break;
case Token.SWITCH:
scope = traverse(n.getFirstChild(), scope);
break;
case Token.VAR:
case Token.RETURN:
case Token.THROW:
scope = traverseChildren(n, scope);
break;
case Token.CATCH:
scope = traverseCatch(n, scope);
break;
}
if (n.getType() != Token.FUNCTION) {
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> JSDocInfo info = n.getJSDocInfo();
if (info != null && info.hasType()) {
JSType castType = info.getType().evaluate(syntacticScope);
// A stubbed type cast on a qualified name should take
// effect for all subsequent accesses of that name,
// so treat it the same as an assign to that name.
if (n.isQualifiedName() &&
n.getParent().getType() == Token.EXPR_RESULT) {
updateScopeForTypeChange(scope, n, n.getJSType(), castType);
}
n.setJSType(castType);
}
}
return scope;
}
/**
* Any value can be thrown, so it's really impossible to determine the type
* of a CATCH param. Treat it as the UNKNOWN type.
*/
private FlowScope traverseCatch(Node n, FlowScope scope) {
Node name = n.getFirstChild();
JSType type = getNativeType(JSTypeNative.UNKNOWN_TYPE);
name.setJSType(type);
redeclare(scope, name.getString(), type);
return scope;
}
private FlowScope traverseAssign(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = getJSType(right);
n.setJSType(rightType);
updateScopeForTypeChange(scope, left, leftType, rightType);
return scope;
}
/**
* Updates the scope according to the result of a type change, like
* an assignment or a type cast.
*/
private void updateScopeForTypeChange(
FlowScope scope, Node left, JSType leftType, JSType resultType) {
Preconditions.checkNotNull(resultType);
switch (left.getType()) {
case Token.NAME:
String varName = left.getString();
Var var = syntacticScope.getVar(varName);
if (var != null && var.isLocal() && var.getScope() != syntacticScope) {
assignedOuterLocalVars.put(var.getScope(), var);
}
// When looking at VAR initializers for declared VARs, we trust
// the declared type over
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>_UNKNOWN_TYPE))) {
n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE));
}
}
return scope;
}
/**
* For functions with function parameters, type inference will set the type of
* a function literal argument from the function parameter type.
*/
private void updateTypeOfParametersOnClosure(Node n, FunctionType fnType) {
int i = 0;
for (Node iParameter : fnType.getParameters()) {
JSType iParameterType = iParameter.getJSType();
if (iParameterType instanceof FunctionType) {
FunctionType iParameterFnType = (FunctionType) iParameterType;
if (i + 1 >= n.getChildCount()) {
// TypeCheck#visitParametersList will warn so we bail.
return;
}
Node iArgument = n.getChildAtIndex(i + 1);
JSType iArgumentType = getJSType(iArgument);
if (iArgument.getType() == Token.FUNCTION &&
iArgumentType instanceof FunctionType &&
iArgumentType.getJSDocInfo() == null) {
iArgument.setJSType(iParameterFnType);
}
}
i++;
}
}
/**
* For functions with function(this: T, ...) and T as parameters, type
* inference will set the type of this on a function literal argument to the
* the actual type of T.
*/
private void updateTypeOfThisOnClosure(Node n, FunctionType fnType) {
// TODO(user): Make the template logic more general.
if (fnType.getTemplateTypeName() == null) {
return;
}
int i = 0;
// Find the parameter whose type is the template type.
for (Node iParameter : fnType.getParameters()) {
JSType iParameterType = getJSType(iParameter);
iParameterType = iParameterType.restrictByNotNullOrUndefined();
if (iParameterType.isTemplateType()) {
// Find the actual type of this argument.
if (i + 1 >= n.getChildCount()) {
// TypeCheck#visitParameterList will warn so we bail.
return;
}
Node iArgument = n.getChildAtIndex(i + 1);
JSType iArgumentType = getJSType(iArgument);
if (iArgumentType != null) {
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
iArgumentType = iArgumentType.restrictByNotNullOrUndefined();
if (!(iArgumentType instanceof ObjectType)) {
compiler.report(
JSError.make(NodeUtil.getSourceName(iArgument), iArgument,
TEMPLATE_TYPE_NOT_OBJECT_TYPE));
return;
}
// Find the parameter whose type is function(this: T, ...)
boolean foundTemplateTypeOfThisParameter = false;
int j = 0;
for (Node jParameter : fnType.getParameters()) {
JSType jParameterType = getJSType(jParameter);
if (jParameterType instanceof FunctionType) {
FunctionType jParameterFnType = (FunctionType) jParameterType;
if (jParameterFnType.getTypeOfThis().equals(iParameterType)) {
foundTemplateTypeOfThisParameter = true;
// Find the actual type of this argument.
if (j + 1 >= n.getChildCount()) {
// TypeCheck#visitParameterList will warn so we bail.
return;
}
Node jArgument = n.getChildAtIndex(j + 1);
JSType jArgumentType = getJSType(jArgument);
if (jArgument.getType() == Token.FUNCTION &&
jArgumentType instanceof FunctionType) {
// If it's an anonymous function, update the type of this
// using the actual type of T.
FunctionType jArgumentFnType =(FunctionType) jArgumentType;
if (jArgumentFnType.getTypeOfThis().isUnknownType()) {
// The new type will be picked up when we traverse the inner
// function.
jArgument.setJSType(
new FunctionType(
registry, jArgumentFnType.getReferenceName(),
jArgumentFnType.getSource(),
jArgumentFnType.getParametersNode(),
jArgumentFnType.getReturnType(),
(ObjectType) iArgumentType));
}
}
// TODO(user): Add code to TypeCheck to check that the
// types of the arguments match.
}
}
j++;
}
if (!foundTemplateTypeOfThisParameter) {
Node source = fnType.getSource();
compiler.report(JSError.make(NodeUtil.getSourceName(source), source,
TEMPLATE_TYPE_OF_THIS_EXPECTED));
return;
}
}
}
i++;
}
}
private FlowScope traverseNew(Node n,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> in which to verify the right node
FlowScope rightScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
left, leftLiterals.getOutcomeFlowScope(left.getType(), condition),
condition);
// type the right node
BooleanOutcomePair rightLiterals =
traverseWithinShortCircuitingBinOp(
right, rightScope.createChildFlowScope());
JSType rightType = right.getJSType();
JSType type;
BooleanOutcomePair literals;
if (leftType != null && rightType != null) {
leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!condition);
if (leftLiterals.toBooleanOutcomes ==
BooleanLiteralSet.get(!condition)) {
// Use the restricted left type, since the right side never gets
// evaluated.
type = leftType;
literals = leftLiterals;
} else {
// Use the join of the restricted left type knowing the outcome of the
// ToBoolean predicate and of the right type.
type = leftType.getLeastSupertype(rightType);
literals =
getBooleanOutcomePair(leftLiterals, rightLiterals, condition);
}
// Exclude the boolean type if the literal set is empty because a boolean
// can never actually be returned.
if (literals.booleanValues == BooleanLiteralSet.EMPTY &&
getNativeType(BOOLEAN_TYPE).isSubtype(type)) {
// Exclusion only make sense for a union type.
if (type instanceof UnionType) {
type = ((UnionType) type).getRestrictedUnion(
getNativeType(BOOLEAN_TYPE));
}
}
} else {
type = null;
literals = new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH,
leftLiterals.getJoinedFlowScope(),
rightLiterals.getJoinedFlowScope());
}
n.setJSType(type);
return literals;
}
private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n,
FlowScope scope) {
switch (n.getType()) {
case Token.AND:
return traverseAnd(n, scope);
case Token.OR:
return traverseOr(n, scope);
default:
scope = traverse(n, scope);
return newBooleanOutcomePair(n.getJSType(), scope);
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>LiteralSet toBooleanOutcomes, BooleanLiteralSet booleanValues,
FlowScope leftScope, FlowScope rightScope) {
this.toBooleanOutcomes = toBooleanOutcomes;
this.booleanValues = booleanValues;
this.leftScope = leftScope;
this.rightScope = rightScope;
}
/**
* Gets the safe estimated scope without knowing if all of the subexpressions
* will be evaluated.
*/
FlowScope getJoinedFlowScope() {
if (joinedScope == null) {
if (leftScope == rightScope) {
joinedScope = rightScope;
} else {
joinedScope = join(leftScope, rightScope);
}
}
return joinedScope;
}
/**
* Gets the outcome scope if we do know the outcome of the entire
* expression.
*/
FlowScope getOutcomeFlowScope(int nodeType, boolean outcome) {
if (nodeType == Token.AND && outcome ||
nodeType == Token.OR && !outcome) {
// We know that the whole expression must have executed.
return rightScope;
} else {
return getJoinedFlowScope();
}
}
}
private BooleanOutcomePair newBooleanOutcomePair(
JSType jsType, FlowScope flowScope) {
if (jsType == null) {
return new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, flowScope, flowScope);
}
return new BooleanOutcomePair(jsType.getPossibleToBooleanOutcomes(),
registry.getNativeType(BOOLEAN_TYPE).isSubtype(jsType) ?
BooleanLiteralSet.BOTH : BooleanLiteralSet.EMPTY,
flowScope, flowScope);
}
private void redeclare(FlowScope scope, String varName, JSType varType) {
if (varType == null) {
varType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
if (unflowableVarNames.contains(varName)) {
return;
}
scope.inferSlotType(varName, varType);
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(n
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (!sourceName.isEmpty()) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
sourceName = "";
curNode = root;
pushScope(root);
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
if (n == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.getType() == Token.FUNCTION) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may be not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
/**
* Gets the compiler.
*/
public Compiler getCompiler() {
// TODO(nicksantos): Remove this type cast. This is just temporary
// while refactoring.
return (Compiler) compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >=0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
return compiler.getInput(sourceName);
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
@SuppressWarnings("
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
traverseBranch(n.getFirstChild(), n);
traverseBranch(n.getFirstChild().getNext().getNext(), n);
break;
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getType() == Token.FUNCTION);
final Node fnName = n.getFirstChild();
boolean anonymous = parent != null && NodeUtil.isFunctionAnonymous(n);
if (!anonymous) {
// Named functions are parent of the containing scope.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (anonymous) {
// Anonymous function names are parent of the contained scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.getType() == Token.BLOCK);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> @SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
scopes.push(s);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return cfgs.peek();
}
/** Returns
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.getValue();
if (value instanceof Boolean) {
map.put(name, ((Boolean) value).booleanValue() ?
new Node(Token.TRUE) : new Node(Token.FALSE));
} else if (value instanceof Integer) {
map.put(name, Node.newNumber(((Integer) value).intValue()));
} else if (value instanceof Double) {
map.put(name, Node.newNumber(((Double) value).doubleValue()));
} else {
Preconditions.checkState(value instanceof String);
map.put(name, Node.newString((String) value));
}
}
return map;
}
/**
* Sets the value of the {@code @define} variable in JS
* to a boolean literal.
*/
public void setDefineToBooleanLiteral(String defineName, boolean value) {
defineReplacements.put(defineName, new Boolean(value));
}
/**
* Sets the value of the {@code @define} variable in JS to a
* String literal.
*/
public void setDefineToStringLiteral(String defineName, String value) {
defineReplacements.put(defineName, value);
}
/**
* Sets the value of the {@code @define} variable in JS to a
* number literal.
*/
public void setDefineToNumberLiteral(String defineName, int value) {
defineReplacements.put(defineName, new Integer(value));
}
/**
* Sets the value of the {@code @define} variable in JS to a
* number literal.
*/
public void setDefineToDoubleLiteral(String defineName, double value) {
defineReplacements.put(defineName, new Double(value));
}
/**
* Skip all possible passes, to make the compiler as fast as possible.
*/
public void skipAllCompilerPasses() {
skipAllPasses = true;
}
/**
* Whether the warnings guard in this Options object enables the given
* group of warnings.
*/
boolean enables(DiagnosticGroup type) {
return warningsGuard != null && warningsGuard.enables(type);
}
/**
* Whether the warnings guard in this Options object disables the given
* group of warnings.
*/
boolean disables(DiagnosticGroup type) {
return warningsGuard != null && warningsGuard.disables(type
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Annotations(compiler, assertOnChange)
.process(externs, root);
}
public static class PropogateConstantAnnotations
extends AbstractPostOrderCallback
implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean assertOnChange;
public PropogateConstantAnnotations(
AbstractCompiler compiler, boolean forbidChanges) {
this.compiler = compiler;
this.assertOnChange = forbidChanges;
}
@Override
public void process(Node externs, Node root) {
new NodeTraversal(compiler, this).traverseRoots(externs, root);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Note: Constant properties annotations are not propagated.
if (n.getType() == Token.NAME) {
if (n.getString().isEmpty()) {
return;
}
JSDocInfo info = null;
// Find the JSDocInfo for a top level variable.
Var var = t.getScope().getVar(n.getString());
if (var != null) {
info = var.getJSDocInfo();
}
if ((info != null && info.isConstant()) &&
!n.getBooleanProp(Node.IS_CONSTANT_NAME)) {
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
if (assertOnChange) {
String name = n.getString();
throw new IllegalStateException(
"Unexpected const change.\n" +
" name: "+ name + "\n" +
" gramps:" + n.getParent().getParent().toStringTree());
}
// Even though the AST has changed (an annotation was added),
// the annotations are not compared so don't report the change.
// reportCodeChange("constant annotation");
}
}
}
}
/**
* Walk the AST tree and verify that constant names are used consistently.
*/
static class VerifyConstants extends AbstractPostOrderCallback
implements CompilerPass {
final private AbstractCompiler compiler;
final private boolean checkUserDeclarations;
VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) {
this.compiler = compiler;
this.checkUserDeclarations = checkUserDeclarations;
}
@Override
public void process(Node externs, Node root) {
Node externsAndJs = root.getParent();
Preconditions.checkState
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(externsAndJs != null);
Preconditions.checkState(externsAndJs.hasChild(externs));
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
private Map<String,Boolean> constantMap = Maps.newHashMap();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
String name = n.getString();
if (n.getString().isEmpty()) {
return;
}
boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (checkUserDeclarations) {
boolean expectedConst = false;
if (NodeUtil.isConstantName(n)
|| compiler.getCodingConvention().isConstant(n.getString())) {
expectedConst = true;
} else {
expectedConst = false;
JSDocInfo info = null;
Var var = t.getScope().getVar(n.getString());
if (var != null) {
info = var.getJSDocInfo();
}
if (info != null && info.isConstant()) {
expectedConst = true;
} else {
expectedConst = false;
}
}
if (expectedConst) {
Preconditions.checkState(expectedConst == isConst,
"The name " + name + " is not annotated as constant.");
} else {
Preconditions.checkState(expectedConst == isConst,
"The name " + name + " should not be annotated as constant.");
}
}
Boolean value = constantMap.get(name);
if (value == null) {
constantMap.put(name, isConst);
} else {
Preconditions.checkState(value.booleanValue() == isConst,
"The name " + name + " is not consistently annotated as " +
"constant.");
}
}
}
}
/**
* Simplify the AST:
* - VAR declarations split, so they represent exactly one child
* declaration.
* - WHILEs are converted to FORs
* - FOR loop are initializers are moved out of the FOR structure
* - LABEL node of children other than LABEL, BLOCK, WHILE, FOR, or DO are
* moved into a block.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> */
static class NormalizeStatements implements Callback {
private final AbstractCompiler compiler;
private final boolean assertOnChange;
NormalizeStatements(AbstractCompiler compiler, boolean assertOnChange) {
this.compiler = compiler;
this.assertOnChange = assertOnChange;
}
private void reportCodeChange(String changeDescription) {
if (assertOnChange) {
throw new IllegalStateException(
"Normalize constraints violated:\n" + changeDescription);
}
compiler.reportCodeChange();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
doStatementNormalizations(t, n, parent);
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.WHILE:
if (CONVERT_WHILE_TO_FOR) {
Node expr = n.getFirstChild();
n.setType(Token.FOR);
Node empty = new Node(Token.EMPTY);
empty.copyInformationFrom(n);
n.addChildBefore(empty, expr);
n.addChildAfter(empty.cloneNode(), expr);
reportCodeChange("WHILE node");
}
break;
case Token.FUNCTION:
normalizeFunctionDeclaration(n);
break;
}
}
/**
* Rewrite named unhoisted functions declarations to a known
* consistent behavior so we don't to different logic paths for the same
* code. From:
* function f() {}
* to:
* var f = function () {};
*/
private void normalizeFunctionDeclaration(Node n) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
if (!NodeUtil.isFunctionAnonymous(n)
&& !NodeUtil.isHoistedFunctionDeclaration(n)) {
rewriteFunctionDeclaration(n);
}
}
/**
* Rewrite the function declaration from:
* function x() {}
* FUNCTION
* NAME
* LP
* BLOCK
* to:
* var x = function() {};
* VAR
* NAME
* FUNCTION
* NAME (w/ empty string)
* LP
* BLOCK
*/
private void rewriteFunctionDeclaration(Node n) {
// Prepare a spot for the function.
Node oldNameNode = n.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>getFirstChild();
Node fnNameNode = oldNameNode.cloneNode();
Node var = new Node(Token.VAR, fnNameNode, n.getLineno(), n.getCharno());
var.copyInformationFrom(n);
// Prepare the function
oldNameNode.setString("");
// Move the function
Node parent = n.getParent();
parent.replaceChild(n, var);
fnNameNode.addChildToFront(n);
reportCodeChange("Function declaration");
}
/**
* Do normalizations that introduce new siblings or parents.
*/
private void doStatementNormalizations(
NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.LABEL) {
normalizeLabels(n);
}
// Only inspect the children of SCRIPTs, BLOCKs and LABELs, as all these
// are the only legal place for VARs and FOR statements.
if (NodeUtil.isStatementBlock(n) || n.getType() == Token.LABEL) {
extractForInitializer(n, null, null);
}
// Only inspect the children of SCRIPTs, BLOCKs, as all these
// are the only legal place for VARs.
if (NodeUtil.isStatementBlock(n)) {
splitVarDeclarations(n);
}
if (n.getType() == Token.FUNCTION) {
moveNamedFunctions(n.getLastChild());
}
}
// TODO(johnlenz): Move this to NodeTypeNormalizer once the unit tests are
// fixed.
/**
* Limit the number of special cases where LABELs need to be handled. Only
* BLOCK and loops are allowed to be labeled. Loop labels must remain in
* place as the named continues are not allowed for labeled blocks.
*/
private void normalizeLabels(Node n) {
Preconditions.checkArgument(n.getType() == Token.LABEL);
Node last = n.getLastChild();
switch (last.getType()) {
case Token.LABEL:
case Token.BLOCK:
case Token.FOR:
case Token.WHILE:
case Token.DO:
return;
default:
Node block = new Node(Token.BLOCK);
block.copyInformationFrom(last);
n.replaceChild(last, block);
block.addChildToFront(last);
reportCode
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>hasChildren()) {
throw new IllegalStateException("Empty VAR node.");
}
while (c.getFirstChild() != c.getLastChild()) {
Node name = c.getFirstChild();
c.removeChild(name);
Node newVar = new Node(
Token.VAR, name, n.getLineno(), n.getCharno());
n.addChildBefore(newVar, c);
reportCodeChange("VAR with multiple children");
}
}
}
}
/**
* Move all the functions that are valid at the execution of the first
* statement of the function to the beginning of the function definition.
*/
private void moveNamedFunctions(Node functionBody) {
Preconditions.checkState(
functionBody.getParent().getType() == Token.FUNCTION);
Node previous = null;
Node current = functionBody.getFirstChild();
// Skip any declarations at the beginning of the function body, they
// are already in the right place.
while (current != null && NodeUtil.isFunctionDeclaration(current)) {
previous = current;
current = current.getNext();
}
// Find any remaining declarations and move them.
Node insertAfter = previous;
while (current != null) {
// Save off the next node as the current node maybe removed.
Node next = current.getNext();
if (NodeUtil.isFunctionDeclaration(current)) {
// Remove the declaration from the body.
Preconditions.checkNotNull(previous);
functionBody.removeChildAfter(previous);
// Readd the function at the top of the function body (after any
// previous declarations).
insertAfter = addToFront(functionBody, current, insertAfter);
reportCodeChange("Move function declaration not at top of function");
} else {
// Update the previous only if the current node hasn't been moved.
previous = current;
}
current = next;
}
}
/**
* @param after The child node to insert the newChild after, or null if
* newChild should be added to the front of parent's child list.
* @return The inserted child node.
*/
private Node addToFront(Node parent, Node newChild, Node after) {
if (after == null) {
parent.addChildToFront(newChild);
} else {
parent.addChildAfter(newChild, after);
}
return newChild;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
}
}
/**
* Remove duplicate VAR declarations.
*/
private void removeDuplicateDeclarations(Node root) {
Callback tickler = new ScopeTicklingCallback();
ScopeCreator scopeCreator = new SyntacticScopeCreator(
compiler, new DuplicateDeclarationHandler());
NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator);
t.traverse(root);
}
/**
* ScopeCreator duplicate declaration handler.
*/
private final class DuplicateDeclarationHandler implements
SyntacticScopeCreator.RedeclarationHandler {
/**
* Remove duplicate VAR declarations encountered discovered during
* scope creation.
*/
@Override
public void onRedeclaration(
Scope s, String name, Node n, Node parent, Node gramps,
Node nodeWithLineNumber) {
Preconditions.checkState(n.getType() == Token.NAME);
Var v = s.getVar(name);
// If name is "arguments", Var maybe null.
Preconditions.checkState(
v == null || v.getParentNode().getType() != Token.CATCH);
if (v != null && parent.getType() == Token.FUNCTION) {
if (v.getParentNode().getType() == Token.VAR) {
s.undeclare(v);
s.declare(name, n, n.getJSType(), v.input);
replaceVarWithAssignment(v.getNameNode(), v.getParentNode(),
v.getParentNode().getParent());
}
} else if (parent.getType() == Token.VAR) {
Preconditions.checkState(parent.hasOneChild());
replaceVarWithAssignment(n, parent, gramps);
}
}
/**
* Remove the parent VAR. There are three cases that need to be handled:
* 1) "var a = b;" which is replaced with "a = b"
* 2) "label:var a;" which is replaced with "label:;". Ideally, the
* label itself would be removed but that is not possible in the
* context in which "onRedeclaration" is called.
* 3) "for (var a in b) ..." which is replaced with "for (a in b)..."
* Cases we don't need to handle are VARs with multiple children,
* which have already been split into separate declarations, so there
*
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> is no need to handle that here, and "for (var a;;);", which has
* been moved out of the loop.
* The result of this is that in each case the parent node is replaced
* which is generally dangerous in a traversal but is fine here with
* the scope creator, as the next node of interest is the parent's
* next sibling.
*/
private void replaceVarWithAssignment(Node n, Node parent, Node gramps) {
if (n.hasChildren()) {
// The * is being initialize, preserve the new value.
parent.removeChild(n);
// Convert "var name = value" to "name = value"
Node value = n.getFirstChild();
n.removeChild(value);
Node replacement = new Node(Token.ASSIGN, n, value);
replacement.copyInformationFrom(parent);
gramps.replaceChild(parent, NodeUtil.newExpr(replacement));
} else {
// It is an empty reference remove it.
if (NodeUtil.isStatementBlock(gramps)) {
gramps.removeChild(parent);
} else if (gramps.getType() == Token.FOR) {
// This is the "for (var a in b)..." case. We don't need to worry
// about initializers in "for (var a;;)..." as those are moved out
// as part of the other normalizations.
parent.removeChild(n);
gramps.replaceChild(parent, n);
} else {
Preconditions.checkState(gramps.getType() == Token.LABEL);
// We should never get here. LABELs with a single VAR statement should
// already have been normalized to have a BLOCK.
throw new IllegalStateException("Unexpected LABEL");
}
}
reportCodeChange("Duplicate VAR declaration");
}
}
/**
* A simple class that causes scope to be created.
*/
private final class ScopeTicklingCallback
implements NodeTraversal.ScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
// Cause the scope to be created, which will cause duplicate
// to be found.
t.getScope();
}
@Override
public void exitScope(NodeTraversal t) {
// Nothing to do.
}
@Override
public boolean
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
public void process(Node externsRoot, Node jsRoot) {
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
inferTypes(externsAndJs);
}
/** Entry point for type inference when running over part of the tree. */
void inferTypes(Node node) {
NodeTraversal inferTypes = new NodeTraversal(
compiler, new TypeInferringCallback(), scopeCreator);
inferTypes.traverseWithScope(node, topScope);
}
private Collection<Var> getUnflowableVars(Scope scope) {
List<Var> vars = Lists.newArrayList();
for (Scope current = scope;
current.isLocal(); current = current.getParent()) {
vars.addAll(escapedLocalVars.get(current));
}
return vars;
}
void inferTypes(NodeTraversal t, Node n, Scope scope) {
TypeInference typeInference =
new TypeInference(
compiler, computeCfg(n), reverseInterpreter, scope,
getUnflowableVars(scope));
try {
typeInference.analyze();
escapedLocalVars.putAll(typeInference.getAssignedOuterLocalVars());
// Resolve any new type names found during the inference.
compiler.getTypeRegistry().resolveTypesInScope(scope);
} catch (DataFlowAnalysis.MaxIterationsExceededException e) {
compiler.report(JSError.make(t, n, DATAFLOW_ERROR));
}
}
private class TypeInferringCallback implements ScopedCallback {
public void enterScope(NodeTraversal t) {
Scope scope = t.getScope();
Node node = t.getCurrentNode();
if (scope.isGlobal()) {
inferTypes(t, node, scope);
}
}
public void exitScope(NodeTraversal t) {
Scope scope = t.getScope();
Node node = t.getCurrentNode();
if (scope.isLocal()) {
inferTypes(t, node, scope);
}
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>();
// Save the tree for later comparison.
Node rootClone = root.cloneTree();
Node externsRootClone = rootClone.getFirstChild();
Node mainRootClone = rootClone.getLastChild();
int numRepetitions = getNumRepetitions();
ErrorManager[] errorManagers = new ErrorManager[numRepetitions];
int aggregateWarningCount = 0;
List<JSError> aggregateWarnings = Lists.newArrayList();
boolean hasCodeChanged = false;
assertFalse("Code should not change before processing",
recentChange.hasCodeChanged());
for (int i = 0; i < numRepetitions; ++i) {
if (compiler.getErrorCount() == 0) {
errorManagers[i] = new BlackHoleErrorManager(compiler);
// Only run the type checking pass once, if asked.
// Running it twice can cause unpredictable behavior because duplicate
// objects for the same type are created, and the type system
// uses reference equality to compare many types.
if (typeCheckEnabled && i == 0) {
TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
check.processForTesting(externsRoot, mainRoot);
}
// Only run the normalize pass once, if asked.
if (normalizeEnabled && i == 0) {
Normalize normalize = new Normalize(compiler, false);
normalize.process(externsRoot, mainRoot);
compiler.setNormalized();
}
if (markNoSideEffects && i == 0) {
MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler);
mark.process(externsRoot, mainRoot);
}
recentChange.reset();
getProcessor(compiler).process(externsRoot, mainRoot);
if (checkLineNumbers) {
(new LineNumberCheck(compiler)).process(externsRoot, mainRoot);
}
hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
aggregateWarningCount += errorManagers[i].getWarningCount();
aggregateWarnings.addAll(Lists.newArrayList(compiler.getWarnings()));
if (normalizeEnabled) {
boolean verifyDeclaredConstants = true;
new Normalize.VerifyConstants(compiler, verifyDeclaredConstants)
.process(externsRoot, mainRoot);
}
}
}
if (error == null) {
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> not be modified.
private boolean frozen = false;
// The last slot defined in this flow instruction, and the head of the
// linked list of slots.
private LinkedFlowSlot lastSlot;
private LinkedFlowScope(FlatFlowScopeCache cache,
LinkedFlowScope directParent) {
this.cache = cache;
if (directParent == null) {
this.lastSlot = null;
this.depth = 0;
this.parent = cache.linkedEquivalent;
} else {
this.lastSlot = directParent.lastSlot;
this.depth = directParent.depth + 1;
this.parent = directParent;
}
}
LinkedFlowScope(FlatFlowScopeCache cache) {
this(cache, null);
}
LinkedFlowScope(LinkedFlowScope directParent) {
this(directParent.cache, directParent);
}
/** Gets the function scope for this flow scope. */
private Scope getFunctionScope() {
return cache.functionScope;
}
/** Whether this flows from a bottom scope. */
private boolean flowsFromBottom() {
return getFunctionScope().isBottom();
}
/**
* Creates an entry lattice for the flow.
*/
public static LinkedFlowScope createEntryLattice(Scope scope) {
return new LinkedFlowScope(new FlatFlowScopeCache(scope));
}
@Override
public void inferSlotType(String symbol, JSType type) {
Preconditions.checkState(!frozen);
lastSlot = new LinkedFlowSlot(symbol, type, lastSlot);
depth++;
cache.dirtySymbols.add(symbol);
}
@Override
public void inferQualifiedSlot(String symbol, JSType bottomType,
JSType inferredType) {
Scope functionScope = getFunctionScope();
if (functionScope.isLocal()) {
if (functionScope.getVar(symbol) == null && !functionScope.isBottom()) {
// When we enter a local scope, many qualified names are
// already defined even if they haven't been declared in the Scope
// object. If the name has not yet been defined in this scope, we
// need to define it now before we refine it.
functionScope.declare(symbol, null, bottomType, null);
}
inferSlotType(symbol, inferredType);
}
}
@Override
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> Join the two types.
// 4) The type is declared in functionScope and joinedScopeB, but
// not in joinedScopeA. Join the two types.
// 5) The type is declared in joinedScopeA and joinedScopeB. Join
// the two types.
Set<String> symbolNames = Sets.newHashSet(symbols.keySet());
symbolNames.addAll(slotsB.keySet());
for (String name : symbolNames) {
StaticSlot<JSType> slotA = slotsA.get(name);
StaticSlot<JSType> slotB = slotsB.get(name);
JSType joinedType = null;
if (slotB == null || slotB.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeB.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #1 -- already inserted.
} else {
// Case #3
joinedType = slotA.getType().getLeastSupertype(fnSlotType);
}
} else if (slotA == null || slotA.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeA.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #2
symbols.put(name, slotB);
} else {
// Case #4
joinedType = slotB.getType().getLeastSupertype(fnSlotType);
}
} else {
// Case #5
joinedType =
slotA.getType().getLeastSupertype(slotB.getType());
}
if (joinedType != null) {
symbols.put(name, new SimpleSlot(name, joinedType, true));
}
}
}
/**
* Get the slot for the given symbol.
*/
public StaticSlot<JSType> getSlot(String name) {
if (symbols.containsKey(name)) {
return symbols.get(name);
} else {
return functionScope.getSlot(name);
}
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>IN_EXTERNS_ERROR =
DiagnosticType.warning(
"JSC_NAME_REFERENCE_IN_EXTERNS",
"accessing name {0} in externs has no effect");
static final DiagnosticType INVALID_FUNCTION_DECL =
DiagnosticType.error("JSC_INVALID_FUNCTION_DECL",
"Syntax error: function declaration must have a name");
private CompilerInput synthesizedExternsInput = null;
private Node synthesizedExternsRoot = null;
private final AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.sanityCheck = sanityCheck;
}
/** {@inheritDoc} */
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
/** {@inheritDoc} */
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
if (NodeUtil.isLabelName(n)) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(NodeUtil.isFunction(parent));
// A function declaration with an empty name passes Rhino,
// but is supposed to be a syntax error according to the spec.
if (!NodeUtil.isAnonymousFunction(parent)) {
t.report(n, INVALID_FUNCTION_DECL);
}
return;
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isAnonymousFunction(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
t.report(n, UNDEFINED_VAR_ERROR, varName);
if
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.GETPROP:
case Token.LP:
// These are okay.
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput == null) {
synthesizedExternsInput =
compiler.newExternInput("{SyntheticVarsDeclar}");
}
return synthesizedExternsInput;
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
CompilerInput synthesizedExterns = getSynthesizedExternsInput();
synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler);
}
return synthesizedExternsRoot;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.traverse(compiler, root, functionListExtractor);
AnonymousFunctionNamer namer = new AnonymousFunctionNamer(functionMap);
AnonymousFunctionNamingCallback namingCallback =
new AnonymousFunctionNamingCallback(namer);
NodeTraversal.traverse(compiler, root, namingCallback);
}
public Iterable<Node> getFunctionNodeList() {
return functionMap.keySet();
}
public int getFunctionId(Node f) {
FunctionRecord record = functionMap.get(f);
if (record != null) {
return record.id;
} else {
return -1;
}
}
public String getFunctionName(Node f) {
FunctionRecord record = functionMap.get(f);
if (record == null) {
// Function node was added during compilation and has no name.
return null;
}
String str = record.name;
if (str.isEmpty()) {
str = "<anonymous>";
}
Node parent = record.parent;
if (parent != null) {
str = getFunctionName(parent) + "::" + str;
}
// this.foo -> foo
str = str.replaceAll("::this\\.", ".");
// foo.prototype.bar -> foo.bar
// AnonymousFunctionNamingCallback already replaces ".prototype."
// with "..", just remove the extra dot.
str = str.replaceAll("\\.\\.", ".");
// remove toplevel anonymous blocks, if they exists.
str = str.replaceFirst("^(<anonymous>::)*", "");
return str;
}
private static class FunctionRecord implements Serializable {
private static final long serialVersionUID = 1L;
public final int id;
public final Node parent;
public String name;
FunctionRecord(int id, Node parent, String name) {
this.id = id;
this.parent = parent;
this.name = name;
}
}
private static class FunctionListExtractor extends AbstractPostOrderCallback {
private final Map<Node, FunctionRecord> functionMap;
private int nextId = 0;
FunctionListExtractor(Map<Node, FunctionRecord> functionMap) {
this.functionMap = functionMap;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
Node functionNameNode =
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> String getName() {
return name;
}
/** Adds a source file input to this module. */
public void add(JSSourceFile file) {
add(new CompilerInput(file));
}
/** Adds a source file input to this module. */
public void addFirst(JSSourceFile file) {
addFirst(new CompilerInput(file));
}
/** Adds a source code input to this module. */
public void add(CompilerInput input) {
inputs.add(input);
input.setModule(this);
}
/** Adds a source code input to this module. */
public void addFirst(CompilerInput input) {
inputs.add(0, input);
input.setModule(this);
}
/** Adds a source code input to this module directly after other. */
public void addAfter(CompilerInput input, CompilerInput other) {
Preconditions.checkState(inputs.contains(other));
inputs.add(inputs.indexOf(other), input);
input.setModule(this);
}
/** Adds a dependency on another module. */
public void addDependency(JSModule dep) {
Preconditions.checkState(dep != this);
deps.add(dep);
}
/** Removes all of the inputs from this module. */
public void removeAll() {
for (CompilerInput input : inputs) {
input.setModule(null);
}
inputs.clear();
}
/**
* Gets the list of modules that this module depends on.
*
* @return A list that may be empty but not null
*/
public List<JSModule> getDependencies() {
return deps;
}
/**
* Returns the transitive closure of dependencies starting from the
* dependencies of this module.
*/
public Set<JSModule> getAllDependencies() {
Set<JSModule> allDeps = Sets.newHashSet(deps);
List<JSModule> workList = Lists.newArrayList(deps);
while (workList.size() > 0) {
JSModule module = workList.remove(workList.size() - 1);
for (JSModule dep : module.getDependencies()) {
if (allDeps.add(dep)) {
workList.add(dep);
}
}
}
return allDeps;
}
/** Returns this
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> dep = provides.get(req);
if (dep != null) {
deps.put(input, dep);
}
}
}
// Sort the JSModule in this order.
List<CompilerInput> sortedList = topologicalStableSort(
inputs, deps);
inputs.clear();
inputs.addAll(sortedList);
}
/**
* Returns the given collection of modules in topological order.
*
* Note that this will return the modules in the same order if they are
* already sorted, and in general, will only change the order as necessary to
* satisfy the ordering dependencies. This can be important for cases where
* the modules do not properly specify all dependencies.
*/
public static JSModule[] sortJsModules(Collection<JSModule> modules) {
final Multimap<JSModule, JSModule> deps = HashMultimap.create();
for (JSModule module : modules) {
for (JSModule dep : module.getDependencies()) {
deps.put(module, dep);
}
}
// Sort the JSModule in this order.
List<JSModule> sortedList = topologicalStableSort(
Lists.newArrayList(modules), deps);
return sortedList.toArray(new JSModule[sortedList.size()]);
}
private static <T> List<T> topologicalStableSort(
List<T> items, Multimap<T, T> deps) {
final Map<T, Integer> originalIndex = Maps.newHashMap();
for (int i = 0; i < items.size(); i++) {
originalIndex.put(items.get(i), i);
}
PriorityQueue<T> inDegreeZero = new PriorityQueue<T>(items.size(),
new Comparator<T>() {
@Override
public int compare(T a, T b) {
return originalIndex.get(a).intValue() -
originalIndex.get(b).intValue();
}
});
List<T> result = Lists.newArrayList();
Multiset<T> inDegree = HashMultiset.create();
Multimap<T, T> reverseDeps = ArrayListMultimap.create();
Multimaps.invertFrom(deps, reverseDeps);
// First, add all the inputs with in-degree 0.
for (T item : items) {
Collection<T
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>stype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.StaticSlot;
import com.google.javascript.rhino.jstype.UnionType;
import com.google.javascript.rhino.jstype.Visitor;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Chainable reverse abstract interpreter providing basic functionality.
*
*
*/
abstract class ChainableReverseAbstractInterpreter
implements ReverseAbstractInterpreter {
protected final CodingConvention convention;
final JSTypeRegistry typeRegistry;
private ChainableReverseAbstractInterpreter firstLink;
private ChainableReverseAbstractInterpreter nextLink;
/**
* Constructs an interpreter, which is the only link in a chain. Interpreters
* can be appended using {@link #append}.
*/
ChainableReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
Preconditions.checkNotNull(convention);
this.convention = convention;
this.typeRegistry = typeRegistry;
firstLink = this;
nextLink = null;
}
/**
* Appends a link to {@code this}, returning the updated last link.
* <p>
* The pattern {@code new X().append(new Y())...append(new Z())} forms a
* chain starting with X, then Y, then ... Z.
* @param lastLink a chainable interpreter, with no next link
* @return the updated last link
*/
ChainableReverseAbstractInterpreter append(
ChainableReverseAbstractInterpreter lastLink) {
Preconditions.checkArgument(lastLink.nextLink == null);
this.nextLink = lastLink;
lastLink.firstLink = this.firstLink;
return lastLink;
}
/**
* Gets the first link of this chain.
*/
ChainableReverseAbstractInterpreter getFirst() {
return firstLink;
}
/**
* Calculates the preciser scope starting with the first link.
*/
protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return firstLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
/**
* Delegates the calculation
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> qualifiedName = node.getQualifiedName();
Preconditions.checkNotNull(qualifiedName);
JSType origType = node.getJSType();
origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType;
scope.inferQualifiedSlot(qualifiedName, origType, type);
break;
default:
throw new IllegalArgumentException("Node cannot be refined. \n" +
node.toStringTree());
}
}
/**
* @see #getRestrictedWithoutUndefined(JSType)
*/
private final Visitor<JSType> restrictUndefinedVisitor =
new Visitor<JSType>() {
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().equals(type)) {
return enumElementType;
} else {
return type;
}
}
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE);
}
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
public JSType caseFunctionType(FunctionType type) {
return type;
}
public JSType caseNullType() {
return getNativeType(NULL_TYPE);
}
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
public JSType caseObjectType(ObjectType type) {
return type;
}
public JSType caseStringType() {
return getNativeType(STRING_TYPE);
}
public JSType caseUnionType(UnionType type) {
return type.getRestrictedUnion(getNativeType(VOID_TYPE));
}
public JSType caseUnknownType() {
return getNativeType(UNKNOWN_TYPE);
}
public JSType caseVoidType() {
return null;
}
};
/**
* @see #getRestrictedWithoutNull(JSType)
*/
private final Visitor<JSType> restrictNullVisitor =
new Visitor<JSType>() {
public JS
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>JSDocInfo inferJSDocInfo = null;
// These fields are used to calculate the percentage of expressions typed.
private int typedCount = 0;
private int nullCount = 0;
private int unknownCount = 0;
private boolean inExterns;
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
Scope topScope,
ScopeCreator scopeCreator,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.reverseInterpreter = reverseInterpreter;
this.typeRegistry = typeRegistry;
this.topScope = topScope;
this.scopeCreator = scopeCreator;
this.reportMissingOverride = reportMissingOverride;
this.reportUnknownTypes = reportUnknownTypes;
this.inferJSDocInfo = new InferJSDocInfo(compiler);
}
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
reportMissingOverride, reportUnknownTypes);
}
TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
CheckLevel.WARNING, CheckLevel.OFF);
}
/** Turn on the missing property check. Returns this for easy chaining. */
TypeCheck reportMissingProperties(boolean report) {
reportMissingProperties = report;
return this;
}
/**
* Main entry point for this phase of processing. This follows the pattern for
* JSCompiler phases.
*
* @param externsRoot The root of the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
public void process(Node externsRoot, Node jsRoot) {
Preconditions.checkNotNull(scopeCreator);
Preconditions.checkNotNull(topScope);
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> != null) {
check(externsRoot, true);
}
check(jsRoot, false);
potentialChecks.flush();
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
JSDocInfo info;
switch (n.getType()) {
case Token.SCRIPT:
case Token.VAR:
// @notypecheck
info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
return false;
}
break;
case Token.FUNCTION:
// @notypecheck
info = n.getJSDocInfo();
info = (info == null) ? parent.getJSDocInfo() : info;
if (info != null && info.isNoTypeCheck()) {
return false;
}
// normal type checking
final TypeCheck outerThis = this;
final Scope outerScope = t.getScope();
final FunctionType functionType = (FunctionType) n.getJSType();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
t.report(n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.LP:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (parent.getType() != Token.FUNCTION) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
if (!isReference(n.getFirstChild())) {
t.report(n, BAD_DELETE);
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(
t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.getType() == Token.OBJECTLIT)
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
break;
default:
t.report(n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
String unresolvedReference = getUnresolvedReference(type);
if (unresolvedReference != null) {
compiler.report(JSError.make(t, n, reportUnknownTypes,
UNRESOLVED_TYPE, unresolvedReference));
} else {
compiler.report(JSError.make(t, n, reportUnknownTypes,
UNKNOWN_EXPR_TYPE));
}
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Looks through the type to see if it contains an unresolved reference. This
* is often the reason that a type is unresolved, and it can occur because of
* a simple misspelling of a type name.
*/
private String getUnresolvedReference(JSType type) {
if (type.isNamedType()) {
NamedType namedType = (NamedType) type;
if (!namedType.is
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isOrdinaryFunction() &&
!(rvalue.isQualifiedName() &&
rvalue.getQualifiedName().equals(abstractMethodName))) {
compiler.report(JSError.make(t, object, INTERFACE_FUNCTION_MEMBERS_ONLY,
abstractMethodName));
}
if (assign.getLastChild().getType() == Token.FUNCTION
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(JSError.make(t, object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* object.property = ...;
* </pre>
* that have an {@code @type} annotation.
*/
private void visitAnnotatedAssignGetprop(NodeTraversal t,
Node assign, JSType type, Node object, String property, Node rvalue) {
// verifying that the rvalue has the correct type
validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type,
object, property);
}
/**
* Visits a NAME node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.LP ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// GETPROP nodes have an assigned type on their node by the scope creator
// if this is an enum declaration. The only namespaced enum declarations
// that we allow are of the form object.name = ...;
if (n.getJSType() != null && parent.getType() == Token.ASSIGN) {
return;
}
// obj.prop or obj.method()
// Lots of types can appear on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
// TODO(user): remove in favor of flagging every property access on
// non-object.
if (!validator.expectNotVoid(t, n, childType,
"undefined has no properties", getNativeType(OBJECT_TYPE))) {
ensureTyped(t, n);
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
return;
}
checkPropertyAccess(childType, property.getString(), t, n);
ensureTyped(t, n);
}
/**
* Make sure that the access of this property is ok.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t, Node n) {
ObjectType objectType = childType.dereference();
if (objectType != null) {
JSType propType = getJSType(n);
if ((!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) &&
propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
t.report(n, INEXISTENT_ENUM_ELEMENT, propName);
} else if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
t.report(n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
} else {
// TODO(nicksantos): might want to flag the access on a non object when
// it's impossible to get a property from this type.
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
}
return false;
}
/**
* Visits a GET
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> attach the UNKNOWN_TYPE.
*/
private void ensureTyped(NodeTraversal t, Node n) {
ensureTyped(t, n, getNativeType(UNKNOWN_TYPE));
}
private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) {
ensureTyped(t, n, getNativeType(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(n.getType() != Token.FUNCTION ||
type instanceof FunctionType ||
type.isUnknownType());
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
JSType infoType = info.getType().evaluate(t.getScope());
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns) {
String propName = n.getType() == Token.GETPROP ?
n.getLastChild().getString() : "(missing)";
compiler.report(
JSError.make(t, n, ILLEGAL_IMPLICIT_CAST, propName));
}
}
if (n.getJSType() == null) {
n.setJSType(type);
}
}
/**
* Returns the percentage of
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
};
}
/**
* Creates a new compiler pass to be run.
*/
final CompilerPass create(AbstractCompiler compiler) {
Preconditions.checkState(!isCreated || !isOneTimePass,
"One-time passes cannot be run multiple times: " + name);
isCreated = true;
return createInternal(compiler);
}
/**
* Creates a new compiler pass to be run.
*/
abstract protected CompilerPass createInternal(AbstractCompiler compiler);
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* Models an assignment that defines a variable and the removal of it.
*
*
*/
class DefinitionsRemover {
/**
* @return an {@link Definition} object if the node contains a definition or
* {@code null} otherwise.
*/
static Definition getDefinition(Node n, Node parent) {
// TODO(user): Since we have parent pointers handy. A lot of constructors
// can be simplied.
if (parent == null) {
return null;
}
if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) {
return new VarDefinition(n);
} else if(NodeUtil.isFunction(parent) && parent.getFirstChild() == n) {
if (!NodeUtil.isAnonymousFunction(parent)) {
return new NamedFunctionDefinition(parent);
} else if (!n.getString().equals("")) {
return new AnonymousFunctionDefinition(parent);
}
} else if (NodeUtil.isAssign(parent) && parent.getFirstChild() == n) {
return new AssignmentDefinition(parent);
} else if (NodeUtil.isObjectLitKey(n, parent)) {
return new ObjectLiteralPropertyDefinition(parent, n, n.getNext());
} else if (parent.getType() == Token.LP) {
Node function = parent.getParent();
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> return new FunctionArgumentDefinition(function, n);
}
return null;
}
static interface Definition {
void remove();
/**
* Variable or property name represented by this definition.
* For example, in the case of assignments this method would
* return the NAME, GETPROP or GETELEM expression that acts as the
* assignment left hand side.
*
* @return the L-Value associated with this definition.
* The node's type is always NAME, GETPROP or GETELEM.
*/
Node getLValue();
/**
* Value expression that acts as the right hand side of the
* definition statement.
*/
Node getRValue();
}
/**
* Represents an name-only external definition. The definition's
* rhs is missing.
*/
abstract static class IncompleteDefinition implements Definition {
private static final Set<Integer> ALLOWED_TYPES =
ImmutableSet.of(Token.NAME, Token.GETPROP, Token.GETELEM);
private final Node lValue;
IncompleteDefinition(Node lValue) {
Preconditions.checkNotNull(lValue);
Preconditions.checkArgument(
ALLOWED_TYPES.contains(lValue.getType()),
"Unexpected lValue type " + Token.name(lValue.getType()));
this.lValue = lValue;
}
@Override
public Node getLValue() {
return lValue;
}
@Override
public Node getRValue() {
return null;
}
}
/**
* Represents an unknown definition.
*/
static final class UnknownDefinition extends IncompleteDefinition {
UnknownDefinition(Node lValue) {
super(lValue);
}
@Override
public void remove() {
throw new IllegalArgumentException("Can't remove an UnknownDefinition");
}
}
/**
* Represents an name-only external definition. The definition's
* rhs is missing.
*/
static final class ExternalNameOnlyDefinition extends IncompleteDefinition {
ExternalNameOnlyDefinition(Node lValue) {
super(lValue);
}
@Override
public void remove() {
throw new IllegalArgumentException(
"Can't remove external name-only definition");
}
}
/**
* Represents an name-only external definition. The definition's
* rhs is missing.
*/
static final class FunctionArgumentDefinition extends IncompleteDefinition {
Function
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>ArgumentDefinition(Node function, Node argumentName) {
super(argumentName);
Preconditions.checkArgument(NodeUtil.isFunction(function));
Preconditions.checkArgument(NodeUtil.isName(argumentName));
}
@Override
public void remove() {
throw new IllegalArgumentException(
"Can't remove a FunctionArgumentDefinition");
}
}
/**
* Represents a function declaration or function expression.
*/
static abstract class FunctionDefinition implements Definition {
protected final Node function;
FunctionDefinition(Node node) {
Preconditions.checkArgument(NodeUtil.isFunction(node));
function = node;
}
@Override
public Node getLValue() {
return function.getFirstChild();
}
@Override
public Node getRValue() {
return function;
}
}
/**
* Represents a function declaration without assignment node such as
* {@code function foo()}.
*/
static final class NamedFunctionDefinition extends FunctionDefinition {
NamedFunctionDefinition(Node node) {
super(node);
}
@Override
public void remove() {
function.detachFromParent();
}
}
/**
* Represents a function expression that acts as a rhs. The defined
* name is only reachable from within the function.
*/
static final class AnonymousFunctionDefinition extends FunctionDefinition {
AnonymousFunctionDefinition(Node node) {
super(node);
Preconditions.checkArgument(
NodeUtil.isAnonymousFunction(node));
}
@Override
public void remove() {
// replace internal name with ""
function.replaceChild(function.getFirstChild(),
Node.newString(Token.NAME, ""));
}
}
/**
* Represents a declaration within an assignment.
*/
static final class AssignmentDefinition implements Definition {
private final Node assignment;
AssignmentDefinition(Node node) {
Preconditions.checkArgument(NodeUtil.isAssign(node));
assignment = node;
}
@Override
public void remove() {
// A simple assignment. foo = bar() -> bar();
Node parent = assignment.getParent();
Node last = assignment.getLastChild();
assignment.removeChild(last);
parent.replaceChild(assignment, last);
}
@Override
public Node getLValue() {
return assignment.getFirstChild();
}
@Override
public Node getRValue() {
return assignment.getLastChild
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>();
}
}
/**
* Represents member declarations using a object literal.
* Example: var x = { e : function() { } };
*/
static final class ObjectLiteralPropertyDefinition implements Definition {
private final Node literal;
private final Node name;
private final Node value;
ObjectLiteralPropertyDefinition(Node lit, Node name, Node value) {
this.literal = lit;
this.name = name;
this.value = value;
}
@Override
public void remove() {
literal.removeChild(name);
literal.removeChild(value);
}
@Override
public Node getLValue() {
// TODO(user) revisit: object literal definitions are an example
// of definitions whose lhs doesn't correspond to a node that
// exists in the AST. We will have to change the return type of
// getLValue sooner or later in order to provide this added
// flexibility.
return new Node(Token.GETPROP,
new Node(Token.OBJECTLIT),
name.cloneNode());
}
@Override
public Node getRValue() {
return value;
}
}
/**
* Represents a VAR declaration with an assignment.
*/
static final class VarDefinition implements Definition {
private final Node name;
VarDefinition(Node node) {
Preconditions.checkArgument(NodeUtil.isVarDeclaration(node));
Preconditions.checkArgument(node.hasChildren(),
"VAR Declaration of " + node.getString() +
"should be assigned a value.");
name = node;
}
@Override
public void remove() {
Node var = name.getParent();
Preconditions.checkState(var.getFirstChild() == var.getLastChild(),
"AST should be normalized first");
Node parent = var.getParent();
Node rValue = name.removeFirstChild();
Preconditions.checkState(parent.getType() != Token.FOR);
parent.replaceChild(var, NodeUtil.newExpr(rValue));
}
@Override
public Node getLValue() {
return name;
}
@Override
public Node getRValue() {
return name.getFirstChild();
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> +
"\nCached : {0}\nActual : {1}");
static final DiagnosticType SCOPE_MISMATCH =
DiagnosticType.error(
"JSC_SCOPE_MISMATCH",
"Scope roots used with the symbol table do not match." +
"\nExpected : {0}\nActual : {1}");
private final AbstractCompiler compiler;
private final ScopeCreator scopeCreator;
// Mutex so that the symbol table may only be acquired by one pass
// at a time.
private boolean locked = false;
// Memoized data with the pass that has currently acquired the
// symbol table.
private MemoizedData cache = null;
SymbolTable(AbstractCompiler compiler) {
this.compiler = compiler;
compiler.addChangeHandler(this);
scopeCreator = new SyntacticScopeCreator(compiler);
}
synchronized void acquire() {
Preconditions.checkState(!locked, "SymbolTable already acquired");
locked = true;
}
synchronized void release() {
Preconditions.checkState(locked, "SymbolTable already released");
locked = false;
}
/**
* Returns the scope at the given node.
*/
@Override
public Scope createScope(Node n, Scope parent) {
// We may only ask for local blocks and the global (all scripts) block.
Preconditions.checkArgument(
(n.getType() == Token.BLOCK && n.getParent() == null) ||
n.getType() == Token.FUNCTION,
"May only create scopes for the global node and functions");
ensureCacheInitialized();
if (!cache.scopes.containsKey(n)) {
cache.scopes.put(n, scopeCreator.createScope(n, parent));
}
return cache.scopes.get(n);
}
/**
* Ensure that the memoization data structures have been initialized.
*/
private void ensureCacheInitialized() {
Preconditions.checkState(locked, "Unacquired symbol table");
if (cache == null) {
cache = new MemoizedData();
}
}
/**
* If the AST changes, and the symbol table has not been acquired, then
* all of our memoized data structures become stale. So delete them.
*/
@Override
public void reportChange() {
if (!locked) {
cache = null;
}
}
/**
* All the
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
Preconditions.checkState(expectedScopes.size() == actualScopes.size());
for (int i = 0; i < expectedScopes.size(); i++) {
Scope expectedScope = expectedScopes.get(i);
Scope actualScope = actualScopes.get(i);
if (!checkNodesMatch(expectedScope.getRootNode(),
actualScope.getRootNode())) {
compiler.report(
JSError.make(
SCOPE_MISMATCH,
expectedScope.getRootNode().toStringTree(),
actualScope.getRootNode().toStringTree()));
continue;
}
if (expectedScope.getVarCount() != actualScope.getVarCount()) {
compiler.report(
JSError.make(
VARIABLE_COUNT_MISMATCH,
Integer.toString(expectedScope.getVarCount()),
Integer.toString(actualScope.getVarCount())));
} else {
Iterator<Var> it = expectedScope.getVars();
while (it.hasNext()) {
Var var = it.next();
Scope.Var actualVar = actualScope.getVar(var.getName());
if (actualVar == null ||
expectedScope.getVar(var.getName()) != var) {
compiler.report(
JSError.make(MISSING_VARIABLE, var.getName()));
} else if (
!checkNodesMatch(
var.getNameNode(),
actualVar.getNameNode()) ||
!isNodeAttached(actualVar.getNameNode())) {
compiler.report(
JSError.make(MOVED_VARIABLE, var.getName()));
}
}
}
}
}
/**
* Check that the two nodes have the same relative position in the tree.
*/
private boolean checkNodesMatch(Node nodeA, Node nodeB) {
Node currentA = nodeA;
Node currentB = nodeB;
while (currentA != null && currentB != null) {
if (currentA.getType() != currentB.getType() ||
!currentA.isEquivalentTo(currentB)) {
return false;
}
currentA = currentA.getParent();
currentB = currentB.getParent();
}
return currentA == null && currentB == null;
}
private boolean isNodeAttached(Node node) {
// Make sure the cached var is still attached.
for (Node current = node;
current != null; current
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Doc(comment, info);
}
}
}
}
// Only after we've seen all @fileoverview entries, attach the
// last one to the root node, and copy the found license strings
// to that node.
if (fileOverviewInfo != null) {
if ((irNode.getJSDocInfo() != null) &&
(irNode.getJSDocInfo().getLicense() != null)) {
fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense());
}
irNode.setJSDocInfo(fileOverviewInfo);
}
}
return irNode;
}
private Node transform(AstNode node) {
String jsDoc = node.getJsDoc();
NodeWithJsDoc nodeWithJsDoc = null;
if (jsDoc != null) {
nodeWithJsDoc = new NodeWithJsDoc();
nodesWithJsDoc.put(jsDoc, nodeWithJsDoc);
}
Node irNode = justTransform(node);
if (nodeWithJsDoc != null) {
nodeWithJsDoc.node = irNode;
}
// If we have a named function, set the position to that of the name.
if (irNode.getType() == Token.FUNCTION &&
irNode.getFirstChild().getLineno() != -1) {
irNode.setLineno(irNode.getFirstChild().getLineno());
irNode.setCharno(irNode.getFirstChild().getCharno());
} else {
if (irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
}
}
return irNode;
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = new Node(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
node.addChildToBack(transform(statementNode.getBreakLabel()));
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = new Node(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
node.addChildToBack(transform(clauseNode.getCatchCondition()));
} else {
Node catchCondition = new Node(Token.EMPTY);
// Old Rhino used the position of the catchVar as the position
// for the (nonexistent) error being caught.
catchCondition.setLineno(catchVar.getLineno());
int clauseAbsolutePosition =
position2charno(catchVar.getAbsolutePosition());
catchCondition.setCharno(clauseAbsolutePosition);
node.addChildToBack(catchCondition);
}
node.addChildToBack(transform(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode) {
return new Node(
Token.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = new Node(Token.CONTINUE);
if (statementNode.getLabel() != null) {
node.addChildToBack(transform(statementNode.getLabel()));
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return new Node(
Token.DO,
transform(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
return new Node(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = new Node
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MUL:
return Token.ASSIGN_MUL;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV:
return Token.ASSIGN_DIV;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MOD:
return Token.ASSIGN_MOD;
case com.google.javascript.jscomp.mozilla.rhino.Token.HOOK:
return Token.HOOK;
case com.google.javascript.jscomp.mozilla.rhino.Token.COLON:
return Token.COLON;
case com.google.javascript.jscomp.mozilla.rhino.Token.OR:
return Token.OR;
case com.google.javascript.jscomp.mozilla.rhino.Token.AND:
return Token.AND;
case com.google.javascript.jscomp.mozilla.rhino.Token.INC:
return Token.INC;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEC:
return Token.DEC;
case com.google.javascript.jscomp.mozilla.rhino.Token.DOT:
return Token.DOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.FUNCTION:
return Token.FUNCTION;
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPORT:
return Token.EXPORT;
case com.google.javascript.jscomp.mozilla.rhino.Token.IMPORT:
return Token.IMPORT;
case com.google.javascript.jscomp.mozilla.rhino.Token.IF:
return Token.IF;
case com.google.javascript.jscomp.mozilla.rhino.Token.ELSE:
return Token.ELSE;
case com.google.javascript.jscomp.mozilla.rhino.Token.SWITCH:
return Token.SWITCH;
case com.google.javascript.jscomp.mozilla.rhino.Token.CASE:
return Token.CASE;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULT:
return Token.DEFAULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.WHILE:
return Token.WHILE;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(source);
assertEquals(expected, noSideEffectCalls);
noSideEffectCalls.clear();
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new NoSideEffectCallEnumerator(compiler);
}
/**
* Run PureFunctionIdentifier, then gather a list of calls that are
* marked as having no side effects.
*/
private class NoSideEffectCallEnumerator
extends AbstractPostOrderCallback implements CompilerPass {
private final Compiler compiler;
NoSideEffectCallEnumerator(Compiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler);
defFinder.process(externs, root);
PureFunctionIdentifier passUnderTest =
new PureFunctionIdentifier(compiler, defFinder);
passUnderTest.process(externs, root);
// Ensure that debug report computation works.
String debugReport = passUnderTest.getDebugReport();
NodeTraversal.traverse(compiler, externs, this);
NodeTraversal.traverse(compiler, root, this);
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NEW) {
if (!NodeUtil.constructorCallHasSideEffects(n)) {
noSideEffectCalls.add(generateNameString(n.getFirstChild()));
}
} else if (n.getType() == Token.CALL) {
if (!NodeUtil.functionCallHasSideEffects(n)) {
noSideEffectCalls.add(generateNameString(n.getFirstChild()));
}
}
}
private String generateNameString(Node node) {
if (node.getType() == Token.OR) {
return "(" + generateNameString(node.getFirstChild()) +
" || " + generateNameString(node.getLastChild()) + ")";
} else if (node.getType() == Token.HOOK) {
return "(" + generateNameString(node.getFirstChild().getNext()) +
" : " + generateNameString(node.getLastChild()) + ")";
} else {
return node.getQualifiedName();
}
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt.LINE;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.SourceExcerptProvider.ExcerptFormatter;
import com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt;
/**
* Lightweight message formatter. The format of messages this formatter
* produces is very compact and to the point.
*
*
*/
public class LightweightMessageFormatter extends AbstractMessageFormatter {
private SourceExcerpt excerpt;
private static final ExcerptFormatter excerptFormatter =
new LineNumberingFormatter();
/**
* A constructor for when the client doesn't care about source information.
*/
private LightweightMessageFormatter() {
super(null);
this.excerpt = LINE;
}
public LightweightMessageFormatter(SourceExcerptProvider source) {
this(source, LINE);
}
public LightweightMessageFormatter(SourceExcerptProvider source,
SourceExcerpt excerpt) {
super(source);
Preconditions.checkNotNull(source);
this.excerpt = excerpt;
}
static LightweightMessageFormatter withoutSource() {
return new LightweightMessageFormatter();
}
public String formatError(JSError error) {
return format(error, false);
}
public String formatWarning(JSError warning) {
return format(warning, true);
}
private String format(JSError error, boolean warning) {
// extract source excerpt
SourceExcerptProvider
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>NativeProperty("toString");
}
/**
* Given the name of a native object property, checks whether the property is
* present on the object and different from the native one.
*/
private boolean hasOverridenNativeProperty(String propertyName) {
if (isNative()) {
return false;
}
JSType propertyType = getPropertyType(propertyName);
ObjectType nativeType =
this.isFunctionType() ?
registry.getNativeObjectType(JSTypeNative.FUNCTION_PROTOTYPE) :
registry.getNativeObjectType(JSTypeNative.OBJECT_PROTOTYPE);
JSType nativePropertyType = nativeType.getPropertyType(propertyName);
return propertyType != nativePropertyType;
}
@Override
public JSType unboxesTo() {
if (isStringObjectType()) {
return getNativeType(JSTypeNative.STRING_TYPE);
} else if (isBooleanObjectType()) {
return getNativeType(JSTypeNative.BOOLEAN_TYPE);
} else if (isNumberObjectType()) {
return getNativeType(JSTypeNative.NUMBER_TYPE);
} else {
return super.unboxesTo();
}
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
public boolean canBeCalled() {
return isRegexpType();
}
/**
* Whether this represents a native type (such as Object, Date,
* RegExp, etc.).
*/
boolean isNative() {
return nativeType;
}
@Override
public String toString() {
return getReferenceName();
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public ObjectType getImplicitPrototype() {
return implicitPrototype;
}
/**
* This should only be reset on the FunctionPrototypeType, only to fix an
* incorrectly established prototype chain due to the user having a mismatch
* in super class declaration, and only before properties on that type are
* processed.
*/
void setImplicitPrototype(ObjectType implicitPrototype) {
checkState(!hasCachedValues());
this.implicitPrototype = implicitPrototype;
}
@Override
public String getReferenceName() {
if (className != null) {
return className;
} else {
return "{...}";
}
}
@Override
public boolean hasReferenceName() {
return className != null;
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>ErrorReporter.forNewRhino(this);
/** Error strings used for reporting JSErrors */
public static final DiagnosticType OPTIMIZE_LOOP_ERROR = DiagnosticType.error(
"JSC_OPTIMIZE_LOOP_ERROR",
"Exceeded max number of optimization iterations: {0}");
public static final DiagnosticType MOTION_ITERATIONS_ERROR =
DiagnosticType.error("JSC_OPTIMIZE_LOOP_ERROR",
"Exceeded max number of code motion iterations: {0}");
private static final long COMPILER_STACK_SIZE = 1048576L;
/**
* Logger for the whole com.google.javascript.jscomp domain -
* setting configuration for this logger affects all loggers
* in other classes within the compiler.
*/
private static final Logger logger =
Logger.getLogger("com.google.javascript.jscomp");
private final PrintStream outStream;
/**
* Creates a Compiler that reports errors and warnings to its logger.
*/
public Compiler() {
this((PrintStream) null);
}
/**
* Creates n Compiler that reports errors and warnings to an output
* stream.
*/
public Compiler(PrintStream stream) {
addChangeHandler(recentChange);
this.typeValidator = new TypeValidator(this);
outStream = stream;
}
/**
* Creates a Compiler that uses a custom error manager.
*/
public Compiler(ErrorManager errorManager) {
this();
setErrorManager(errorManager);
}
/**
* Acquires the symbol table.
*/
@Override
SymbolTable acquireSymbolTable() {
if (symbolTable == null) {
symbolTable = new SymbolTable(this);
}
symbolTable.acquire();
return symbolTable;
}
/**
* Sets the error manager.
*
* @param errorManager the error manager, it cannot be {@code null}
*/
public void setErrorManager(ErrorManager errorManager) {
Preconditions.checkNotNull(
errorManager, "the error manager cannot be null");
this.errorManager = errorManager;
}
/**
* Creates a message formatter instance corresponding to the value of
* {@link CompilerOptions}.
*/
private MessageFormatter createMessageFormatter() {
boolean colorize = options.shouldColorizeErrorOutput();
return options.errorFormat.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> {
String name = input.getName();
if (!inputsByName.containsKey(name)) {
inputsByName.put(name, input);
} else {
report(JSError.make(DUPLICATE_INPUT, name));
}
}
}
public Result compile(
JSSourceFile extern, JSSourceFile input, CompilerOptions options) {
return compile(extern, new JSSourceFile[] { input }, options);
}
public Result compile(
JSSourceFile extern, JSSourceFile[] input, CompilerOptions options) {
return compile(new JSSourceFile[] { extern }, input, options);
}
public Result compile(
JSSourceFile extern, JSModule[] modules, CompilerOptions options) {
return compile(new JSSourceFile[] { extern }, modules, options);
}
/**
* Compiles a list of inputs.
*/
public Result compile(JSSourceFile[] externs,
JSSourceFile[] inputs,
CompilerOptions options) {
// The compile method should only be called once.
Preconditions.checkState(jsRoot == null);
try {
init(externs, inputs, options);
if (hasErrors()) {
return getResult();
}
return compile();
} finally {
Tracer t = newTracer("generateReport");
errorManager.generateReport();
stopTracer(t, "generateReport");
}
}
/**
* Compiles a list of modules.
*/
public Result compile(JSSourceFile[] externs,
JSModule[] modules,
CompilerOptions options) {
// The compile method should only be called once.
Preconditions.checkState(jsRoot == null);
try {
init(externs, modules, options);
if (hasErrors()) {
return getResult();
}
return compile();
} finally {
Tracer t = newTracer("generateReport");
errorManager.generateReport();
stopTracer(t, "generateReport");
}
}
private Result compile() {
return runInCompilerThread(new Callable<Result>() {
public Result call() throws Exception {
compileInternal();
return getResult();
}
});
}
/**
* Disable threads. This is for clients that run on AppEngine and
* don't have threads.
*/
public void disable
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.
if (!options.ideMode) {
optimize();
}
}
if (options.recordFunctionInformation) {
recordFunctionInformation();
}
if (options.devMode == DevMode.START_AND_END) {
runSanityCheck();
}
}
public void parse() {
parseInputs();
}
PassConfig getPassConfig() {
if (passes == null) {
passes = createPassConfigInternal();
}
return passes;
}
/**
* Create the passes object. Clients should use setPassConfig instead of
* overriding this.
*/
PassConfig createPassConfigInternal() {
return new DefaultPassConfig(options);
}
/**
* @param passes The PassConfig to use with this Compiler.
* @throws NullPointerException if passes is null
* @throws IllegalStateException if this.passes has already been assigned
*/
public void setPassConfig(PassConfig passes) {
// Important to check for null because if setPassConfig(null) is
// called before this.passes is set, getPassConfig() will create a
// new PassConfig object and use that, which is probably not what
// the client wanted since he or she probably meant to use their
// own PassConfig object.
Preconditions.checkNotNull(passes);
if (this.passes != null) {
throw new IllegalStateException("this.passes has already been assigned");
}
this.passes = passes;
}
/**
* Carry out any special checks or procedures that need to be done before
* proceeding with rest of the compilation process.
*
* @return true, to continue with compilation
*/
boolean precheck() {
return true;
}
public void check() {
runCustomPasses(CustomPassExecutionTime.BEFORE_CHECKS);
PhaseOptimizer phaseOptimizer = new PhaseOptimizer(this, tracker);
if (options.devMode == DevMode.EVERY_PASS) {
phaseOptimizer.setSanityCheck(sanityCheck);
}
phaseOptimizer.consume(getPassConfig().getChecks());
phaseOptimizer.process(externsRoot, jsRoot);
if (hasErrors()) {
return;
}
// TODO(nicksantos): clean this up. The flow here is too hard to follow.
if (options.nameAnonymousFunctionsOnly) {
return;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>> stripNamePrefixes) {
logger.info("Strip code");
startPass("stripCode");
StripCode r = new StripCode(this, stripTypes, stripNameSuffixes,
stripTypePrefixes, stripNamePrefixes);
process(r);
endPass();
}
/**
* Runs custom passes that are designated to run at a particular time.
*/
private void runCustomPasses(CustomPassExecutionTime executionTime) {
if (options.customPasses != null) {
Tracer t = newTracer("runCustomPasses");
try {
for (CompilerPass p : options.customPasses.get(executionTime)) {
process(p);
}
} finally {
stopTracer(t, "runCustomPasses");
}
}
}
private Tracer currentTracer = null;
private String currentPassName = null;
/**
* Marks the beginning of a pass.
*/
void startPass(String passName) {
Preconditions.checkState(currentTracer == null);
currentPassName = passName;
currentTracer = newTracer(passName);
}
/**
* Marks the end of a pass.
*/
void endPass() {
Preconditions.checkState(currentTracer != null,
"Tracer should not be null at the end of a pass.");
stopTracer(currentTracer, currentPassName);
String passToCheck = currentPassName;
currentPassName = null;
currentTracer = null;
maybeSanityCheck();
}
/**
* Returns a new tracer for the given pass name.
*/
Tracer newTracer(String passName) {
String comment = passName
+ (recentChange.hasCodeChanged() ? " on recently changed AST" : "");
if (options.tracer.isOn()) {
tracker.recordPassStart(passName);
}
return new Tracer("Compiler", comment);
}
void stopTracer(Tracer t, String passName) {
long result = t.stop();
if (options.tracer.isOn()) {
tracker.recordPassStop(passName, result);
}
}
/**
* Returns the result of the compilation.
*/
public Result getResult() {
PassConfig.State state = getPassConfig().getIntermediateState();
return new Result(getErrors(),
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
}
}
//------------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------------
// TODO(nicksantos): Decide which parts of these belong in an AbstractCompiler
// interface, and which ones should always be injected.
@Override
public CompilerInput getInput(String name) {
return inputsByName.get(name);
}
@Override
public CompilerInput newExternInput(String name) {
if (inputsByName.containsKey(name)) {
throw new IllegalArgumentException("Conflicting externs name: " + name);
}
SourceAst ast = new SyntheticAst(name);
CompilerInput input = new CompilerInput(ast, name, true);
inputsByName.put(name, input);
externsRoot.addChildToFront(ast.getAstRoot(this));
return input;
}
/** Add a source input dynamically. Intended for incremental compilation. */
void addIncrementalSourceAst(JsAst ast) {
String sourceName = ast.getSourceFile().getName();
Preconditions.checkState(
getInput(sourceName) == null,
"Duplicate input of name " + sourceName);
inputsByName.put(sourceName, new CompilerInput(ast));
}
@Override
JSModuleGraph getModuleGraph() {
return moduleGraph;
}
@Override
public JSTypeRegistry getTypeRegistry() {
if (typeRegistry == null) {
typeRegistry = new JSTypeRegistry(oldErrorReporter);
}
return typeRegistry;
}
@Override
ScopeCreator getScopeCreator() {
return getPassConfig().getScopeCreator();
}
@Override
public Scope getTopScope() {
return getPassConfig().getTopScope();
}
@Override
public ReverseAbstractInterpreter getReverseAbstractInterpreter() {
if (abstractInterpreter == null) {
ChainableReverseAbstractInterpreter interpreter =
new SemanticReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry());
if (options.closurePass) {
interpreter = new ClosureReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry())
.append(interpreter).getFirst();
}
abstractInterpreter = interpreter;
}
return abstractInterpreter;
}
@Override
TypeValidator getTypeValidator() {
return typeValidator;
}
//------------------------------------------------------------------------
// Parsing
//------------------------------------------------------------------------
/**
* Parses the externs and main inputs.
*
* @
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> IllegalArgumentException(
"Bad module input: " + inputs.get(i).getName());
}
cb.reset();
toSource(cb, i, scriptNode);
sources[i] = cb.toString();
}
return sources;
}
});
}
/**
* Writes out js code from a root node. If printing input delimiters, this
* method will attach a comment to the start of the text indicating which
* input the output derived from. If there were any preserve annotations
* within the root's source, they will also be printed in a block comment
* at the beginning of the output.
*/
public void toSource(final CodeBuilder cb,
final int inputSeqNum,
final Node root) {
runInCompilerThread(new Callable<Void>() {
public Void call() throws Exception {
if (options.printInputDelimiter) {
if ((cb.getLength() > 0) && !cb.endsWith("\n")) {
cb.append("\n"); // Make sure that the label starts on a new line
}
Preconditions.checkState(root.getType() == Token.SCRIPT);
String delimiter = options.inputDelimiter;
String sourceName = (String)root.getProp(Node.SOURCENAME_PROP);
Preconditions.checkState(sourceName != null);
Preconditions.checkState(!sourceName.isEmpty());
delimiter = delimiter.replaceAll("%name%", sourceName)
.replaceAll("%num%", String.valueOf(inputSeqNum));
cb.append(delimiter)
.append("\n");
}
if (root.getJSDocInfo() != null &&
root.getJSDocInfo().getLicense() != null) {
cb.append("/*\n")
.append(root.getJSDocInfo().getLicense())
.append("*/\n");
}
// If there is a valid source map, then indicate to it that the current
// root node's mappings are offset by the given string builder buffer.
if (options.sourceMapOutputPath != null) {
sourceMap.setStartingPosition(
cb.getLineIndex(), cb.getColumnIndex());
}
String code = toSource(root);
if (!code.isEmpty()) {
cb.append(code);
if (!code.endsWith(";")) {
cb.append(";");
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.parsing.ParserRunner;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Generates an AST for a JavaScript source file.
*
*
*/
public class JsAst implements SourceAst {
private static final Logger logger_ = Logger.getLogger(JsAst.class.getName());
private static final long serialVersionUID = 1L;
private transient SourceFile sourceFile;
private String fileName;
private Node root;
public JsAst(SourceFile sourceFile) {
this.sourceFile = sourceFile;
this.fileName = sourceFile.getName();
}
@Override
public Node getAstRoot(AbstractCompiler compiler) {
if (root == null) {
createAst(compiler);
}
return root;
}
@Override
public void clearAst() {
root = null;
// While we're at it, clear out any saved text in the source file on
// the assumption that if we're dumping the parse tree, then we probably
// assume regenerating everything else is a smart idea also.
sourceFile.clearCachedSource();
}
@Override
public SourceFile getSourceFile() {
return sourceFile;
}
@Override
public void setSourceFile(SourceFile file) {
Preconditions.checkState(fileName.equals(file.getName()));
sourceFile = file;
}
private void createAst(AbstractCompiler compiler)
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Group ...groups) {
Set<DiagnosticType> set = Sets.newHashSet();
for (DiagnosticGroup group : groups) {
set.addAll(group.types);
}
this.types = ImmutableSet.copyOf(set);
}
/**
* Returns whether the given error's type matches a type
* in this group.
*/
public boolean matches(JSError error) {
return matches(error.getType());
}
/**
* Returns whether the given type matches a type in this group.
*/
public boolean matches(DiagnosticType type) {
return types.contains(type);
}
/**
* Returns whether all of the types in the given group are in this group.
*/
boolean isSubGroup(DiagnosticGroup group) {
for (DiagnosticType type : group.types) {
if (!matches(type)) {
return false;
}
}
return true;
}
/**
* Returns an iterator over all the types in this group.
*/
Collection<DiagnosticType> getTypes() {
return types;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
/**
* Create a named type based on the reference.
*/
public NamedType(JSTypeRegistry registry, String reference,
String sourceName, int lineno, int charno) {
super(registry, registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE));
Preconditions.checkNotNull(reference);
this.reference = reference;
this.sourceName = sourceName;
this.lineno = lineno;
this.charno = charno;
}
@Override
public void forgiveUnknownNames() {
forgiving = true;
}
/** Returns the type to which this refers (which is unknown if unresolved). */
public JSType getReferencedType() {
return referencedType;
}
@Override
public String getReferenceName() {
return reference;
}
@Override
public String toString() {
return reference;
}
@Override
public boolean hasReferenceName() {
return true;
}
@Override
public boolean isNamedType() {
return true;
}
@Override
public boolean isNominalType() {
return true;
}
/**
* Two named types are equal if they are the same {@code ObjectType} object.
* This is complicated by the fact that equals is sometimes called before we
* have a chance to resolve the type names.
*
* @return {@code true} iff {@code that} == {@code this} or {@code that}
* is a {@link NamedType} whose reference is the same as ours,
* or {@code that} is the type we reference.
*/
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
} else if (that instanceof JSType) {
ObjectType objType = ObjectType.cast((JSType) that);
if (objType != null) {
return objType.isNominalType() &&
reference.equals(objType.getReferenceName());
}
}
return false;
}
@Override
public int hashCode() {
return reference.hashCode();
}
/**
* Resolve the referenced type within the enclosing scope.
*/
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
// TODO(user): Investigate whether it is really necessary to keep two
// different
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
sourceName = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.getType() == Token.FUNCTION) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionAnonymous(n)) {
declareVar(fnName, fnNameNode, n, null, null, n);
}
// Args: Declare function variables
Preconditions.checkState(args.getType() == Token.LP);
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.getType() == Token.NAME);
declareVar(a.getString(), a, args, n, null, n);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
Preconditions.checkState(child.getType() == Token.NAME);
String name = child.getString();
declareVar(name, child, n, parent, null, n);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionAnonymous(n)) {
return;
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(fnName, n.getFirstChild(), n, parent, null, n);
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext().getNext();
declareVar(var.getString(), var, n, parent, null, n);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
public void onRedeclaration(
Scope s, String name,
Node n, Node parent, Node gramps, Node nodeWithLineNumber) {
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.getType() == Token.CATCH &&
parent.getType() == Token.CATCH) {
// Okay, both are 'catch(x)' variables.
return
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
}
static interface JoinOp<L extends LatticeElement>
extends Function<List<L>, L> {
}
/**
* An implementation of {@code JoinOp} that makes it easy to join to
* lattice elements at a time.
*/
static abstract class BinaryJoinOp<L extends LatticeElement>
implements JoinOp<L> {
@Override
public final L apply(List<L> values) {
Preconditions.checkArgument(!values.isEmpty());
int size = values.size();
if (size == 1) {
return values.get(0);
} else if (size == 2) {
return apply(values.get(0), values.get(1));
} else {
int mid = computeMidPoint(size);
return apply(
apply(values.subList(0, mid)),
apply(values.subList(mid, size)));
}
}
/**
* Creates a new lattice that will be the join of two input lattices.
*
* @return The join of {@code latticeA} and {@code latticeB}.
*/
abstract L apply(L latticeA, L latticeB);
/**
* Finds the midpoint of a list. The function will favor two lists of
* even length instead of two lists of the same odd length. The list
* must be at least length two.
*
* @param size Size of the list.
*/
static int computeMidPoint(int size) {
int midpoint = size >>> 1;
if (size > 4) {
/* Any list longer than 4 should prefer an even split point
* over the true midpoint, so that [0,6] splits at 2, not 3. */
midpoint &= -2; // (0xfffffffe) clears low bit so midpoint is even
}
return midpoint;
}
}
private final ControlFlowGraph<N> cfg;
final JoinOp<L> joinOp;
protected final Set<DiGraphNode<N, Branch>> orderedWorkSet;
/*
* Feel free to increase this to a reasonable number if you are finding that
* more and more passes need more than 100000 steps before finding a
* fixed-point. If you just have a special case, consider calling
*
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
private L out;
/**
* Private constructor. No other classes should create new states.
*
* @param inState Input.
* @param outState Output.
*/
private FlowState(L inState, L outState) {
Preconditions.checkNotNull(inState);
Preconditions.checkNotNull(outState);
this.in = inState;
this.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
L getOut() {
return out;
}
void setOut(L out) {
Preconditions.checkNotNull(out);
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* The exception to be thrown if the analysis has been running for a long
* number of iterations. Chances are the analysis is not monotonic, a
* fixed-point cannot be found and it is currently stuck in an infinite loop.
*/
static class MaxIterationsExceededException extends RuntimeException {
private static final long serialVersionUID = 1L;
MaxIterationsExceededException(String msg) {
super(msg);
}
}
abstract static class BranchedForwardDataFlowAnalysis
<N, L extends LatticeElement> extends DataFlowAnalysis<N, L> {
@Override
protected void initialize() {
orderedWorkSet.clear();
for (DiGraphNode<N, Branch> node : getCfg().getDirectedGraphNodes()) {
List<DiGraphEdge<N, Branch>> edgeList =
getCfg().getOutEdges(node.getValue());
int outEdgeCount = edgeList.size();
List<L> outLattices = Lists.newArrayList();
for (int i = 0; i < outEdgeCount; i++) {
outLattices.add(createInitialEstimateLattice());
}
node.setAnnotation(new BranchedFlowState<L>(
createInitialEstimateLattice(), outLattices));
if (node != getCfg().getImplicitReturn()) {
orderedWorkSet
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.add(node);
}
}
}
BranchedForwardDataFlowAnalysis(ControlFlowGraph<N> targetCfg,
JoinOp<L> joinOp) {
super(targetCfg, joinOp);
}
/**
* Returns the lattice element at the exit point. Needs to be overridden
* because we use a BranchedFlowState instead of a FlowState; ugh.
*/
@Override
L getExitLatticeElement() {
DiGraphNode<N, Branch> node = getCfg().getImplicitReturn();
BranchedFlowState<L> state = node.getAnnotation();
return state.getIn();
}
@Override
final boolean isForward() {
return true;
}
/**
* The branched flow function maps a single lattice to a list of output
* lattices.
*
* <p>Each outgoing edge of a node will have a corresponding output lattice
* in the ordered returned by
* {@link com.google.javascript.jscomp.graph.DiGraph#getOutEdges(Object)}
* in the returned list.
*
* @return A list of output values depending on the edge's branch type.
*/
abstract List<L> branchedFlowThrough(N node, L input);
@Override
protected final boolean flow(DiGraphNode<N, Branch> node) {
BranchedFlowState<L> state = node.getAnnotation();
List<L> outBefore = state.out;
state.out = branchedFlowThrough(node.getValue(), state.in);
Preconditions.checkState(outBefore.size() == state.out.size());
for (int i = 0; i < outBefore.size(); i++) {
if (!outBefore.get(i).equals(state.out.get(i))) {
return true;
}
}
return false;
}
@Override
protected void joinInputs(DiGraphNode<N, Branch> node) {
BranchedFlowState<L> state = node.getAnnotation();
List<DiGraphNode<N, Branch>> predNodes =
getCfg().getDirectedPredNodes(node);
List<L> values = new ArrayList<L>(predNodes.size());
for (DiGraphNode<N, Branch> predNode :
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> predNodes) {
BranchedFlowState<L> predNodeState = predNode.getAnnotation();
L in = predNodeState.out.get(
getCfg().getDirectedSuccNodes(predNode).indexOf(node));
values.add(in);
}
if (getCfg().getEntry() == node) {
state.setIn(createEntryLattice());
} else if (!values.isEmpty()) {
state.setIn(joinOp.apply(values));
}
}
}
/**
* The in and out states of a node.
*
* @param <L> Input and output lattice element type.
*/
static class BranchedFlowState<L extends LatticeElement>
implements Annotation {
private L in;
private List<L> out;
/**
* Private constructor. No other classes should create new states.
*
* @param inState Input.
* @param outState Output.
*/
private BranchedFlowState(L inState, List<L> outState) {
Preconditions.checkNotNull(inState);
Preconditions.checkNotNull(outState);
this.in = inState;
this.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
List<L> getOut() {
return out;
}
void setOut(List<L> out) {
Preconditions.checkContentsNotNull(out);
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* Compute set of escaped variables. When a variable is escaped in a
* dataflow analysis, it can be reference outside of the code that we are
* analyzing. A variable is escaped if any of the following is true:
*
* <p><ol>
* <li>It is defined as the exception name in CATCH clause so it became a
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2004 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
/**
* Utility class that extracts the qualified name out of a node.
* Useful when trying to get a human-friendly string representation of
* a property node that can be used to describe the node or name
* related nodes based on it (as done by the NameAnonymousFunctions
* compiler pass).
*
*
*/
class NodeNameExtractor {
private final char delimiter;
private int nextUniqueInt = 0;
NodeNameExtractor(char delimiter) {
this.delimiter = delimiter;
}
/**
* Returns a qualified name of the specified node. Dots and brackets
* are changed to the delimiter passed in when constructing the
* NodeNameExtractor object. We also replace ".prototype" with the
* delimiter to keep names short, while still differentiating them
* from static properties. (Prototype properties will end up
* looking like "a$b$$c" if this.delimiter = '$'.)
*/
String getName(Node node) {
switch (node.getType()) {
case Token.FUNCTION:
Node functionNameNode = node.getFirstChild();
return functionNameNode.getString();
case Token.GETPROP:
Node lhsOfDot = node.getFirstChild();
Node rhsOfDot = lhsOfDot.getNext();
String lhsOfDotName = getName(lhsOfDot);
String rhsOfDotName = getName(rhsOfDot);
if ("prototype".equals(rhsOfDot
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
LB = 79, // left and right brackets
RB = 80,
LC = 81, // left and right curlies (braces)
RC = 82,
LP = 83, // left and right parentheses
RP = 84,
COMMA = 85, // comma operator
ASSIGN = 86, // simple assignment (=)
ASSIGN_BITOR = 87, // |=
ASSIGN_BITXOR = 88, // ^=
ASSIGN_BITAND = 89, // |=
ASSIGN_LSH = 90, // <<=
ASSIGN_RSH = 91, // >>=
ASSIGN_URSH = 92, // >>>=
ASSIGN_ADD = 93, // +=
ASSIGN_SUB = 94, // -=
ASSIGN_MUL = 95, // *=
ASSIGN_DIV = 96, // /=
ASSIGN_MOD = 97; // %=
public final static int
FIRST_ASSIGN = ASSIGN,
LAST_ASSIGN = ASSIGN_MOD,
HOOK = 98, // conditional (?:)
COLON = 99,
OR = 100, // logical or (||)
AND = 101, // logical and (&&)
INC = 102, // increment/decrement (++ --)
DEC = 103,
DOT = 104, // member operator (.)
FUNCTION = 105, // function keyword
EXPORT = 106, // export keyword
IMPORT = 107, // import keyword
IF = 108, // if keyword
ELSE = 109, // else keyword
SWITCH = 110, // switch keyword
CASE = 111, // case keyword
DEFAULT = 112, // default keyword
WHILE = 113, // while keyword
DO = 114, // do keyword
FOR = 11
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
case REF_NAME: return "REF_NAME";
case REF_NS_NAME: return "REF_NS_NAME";
case TRY: return "TRY";
case SEMI: return "SEMI";
case LB: return "LB";
case RB: return "RB";
case LC: return "LC";
case RC: return "RC";
case LP: return "LP";
case RP: return "RP";
case COMMA: return "COMMA";
case ASSIGN: return "ASSIGN";
case ASSIGN_BITOR: return "ASSIGN_BITOR";
case ASSIGN_BITXOR: return "ASSIGN_BITXOR";
case ASSIGN_BITAND: return "ASSIGN_BITAND";
case ASSIGN_LSH: return "ASSIGN_LSH";
case ASSIGN_RSH: return "ASSIGN_RSH";
case ASSIGN_URSH: return "ASSIGN_URSH";
case ASSIGN_ADD: return "ASSIGN_ADD";
case ASSIGN_SUB: return "ASSIGN_SUB";
case ASSIGN_MUL: return "ASSIGN_MUL";
case ASSIGN_DIV: return "ASSIGN_DIV";
case ASSIGN_MOD: return "ASSIGN_MOD";
case HOOK: return "HOOK";
case COLON: return "COLON";
case OR: return "OR";
case AND: return "AND";
case INC: return "INC";
case DEC: return "DEC";
case DOT: return "DOT";
case FUNCTION: return "FUNCTION";
case EXPORT: return "EXPORT";
case IMPORT: return "IMPORT";
case IF: return "IF";
case ELSE: return "ELSE";
case SWITCH: return "SWITCH";
case CASE: return "CASE";
case DEFAULT: return "DEFAULT";
case WHILE: return "WHILE";
case DO: return "DO";
case FOR: return "FOR";
case BREAK: return "BREAK";
case CONTINUE: return "CONTINUE";
case VAR: return "VAR";
case WITH: return "WITH";
case CATCH: return "CATCH";
case
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.
* @return A clone of the function body mutated to be suitable for injection
* as a statement into another code block.
*/
Node mutate(String fnName, Node fnNode, Node callNode,
String resultName, boolean needsDefaultResult, boolean isCallInLoop) {
Node newFnNode = fnNode.cloneTree();
// Now that parameter names have been replaced, make sure all the local
// names are unique, to allow functions to be inlined multiple times
// without causing conflicts.
makeLocalNamesUnique(newFnNode, isCallInLoop);
// TODO(johnlenz): Mark NAME nodes constant for parameters that are not
// modified.
Set<String> namesToAlias =
FunctionArgumentInjector.findModifiedParameters(newFnNode);
LinkedHashMap<String, Node> args =
FunctionArgumentInjector.getFunctionCallParameterMap(
newFnNode, callNode, this.safeNameIdSupplier);
boolean hasArgs = !args.isEmpty();
if (hasArgs) {
FunctionArgumentInjector.maybeAddTempsForCallArguments(
newFnNode, args, namesToAlias, compiler.getCodingConvention());
}
Node newBlock = NodeUtil.getFunctionBody(newFnNode);
// Make the newBlock insertable .
newBlock.detachFromParent();
if (hasArgs) {
Node inlineResult = aliasAndInlineArguments(newBlock,
args, namesToAlias);
Preconditions.checkState(newBlock == inlineResult);
}
//
// For calls inlined into loops, VAR declarations are not reinitialized to
// undefined as they would have been if the function were called, so ensure
// that they are properly initialized.
//
if (isCallInLoop) {
fixUnitializedVarDeclarations(newBlock);
}
String labelName = getLabelNameForFunction(fnName);
Node injectableBlock = replaceReturns(
newBlock, resultName, labelName, needsDefaultResult);
Preconditions.checkState(injectableBlock != null);
return injectableBlock;
}
/**
* For all VAR node with uninitialized declarations, set
* the values to be "undefined".
*/
private void fixUnitializedVarDeclarations(Node n) {
// Inner loop structure must already have logic to initialize its
// variables. In
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> particular FOR-IN structures must not be modified.
if (NodeUtil.isLoopStructure(n)) {
return;
}
// For all VARs
if (NodeUtil.isVar(n)) {
Node name = n.getFirstChild();
// It isn't initialized.
if (!name.hasChildren()) {
name.addChildToBack(NodeUtil.newUndefinedNode());
}
return;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
fixUnitializedVarDeclarations(c);
}
}
/**
* Fix-up all local names to be unique for this subtree.
* @param fnNode A mutable instance of the function to be inlined.
*/
private void makeLocalNamesUnique(Node fnNode, boolean isCallInLoop) {
NodeTraversal.traverse(
compiler, fnNode, new MakeDeclaredNamesUnique(
new InlineRenamer(
compiler.getUniqueNameIdSupplier(),
"inline_",
isCallInLoop)));
}
/**
* Create a unique label name.
*/
private String getLabelNameForFunction(String fnName){
String name = (fnName == null || fnName.isEmpty()) ? "anon" : fnName;
return "JSCompiler_inline_label_" + name + "_" + safeNameIdSupplier.get();
}
/**
* Inlines the arguments within the node tree using the given argument map,
* replaces "unsafe" names with local aliases.
*
* The aliases for unsafe require new VAR declarations, so this function
* can not be used in for direct CALL node replacement as VAR nodes can not be
* created there.
*
* @return The node or its replacement.
*/
private Node aliasAndInlineArguments(
Node fnTemplateRoot, LinkedHashMap<String, Node> argMap,
Set<String> namesToAlias) {
if (namesToAlias == null || namesToAlias.isEmpty()) {
// There are no names to alias, just inline the arguments directly.
Node result = FunctionArgumentInjector.inject(
fnTemplateRoot, null, argMap);
Preconditions.checkState(result == fnTemplateRoot);
return result;
} else {
// Create local alias of names that can not be safely
// used directly.
// An arg map that will
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> be updated to contain the
// safe aliases.
Map<String, Node> newArgMap = Maps.newHashMap(argMap);
// Declare the alias in the same order as they
// are declared.
List<Node> newVars = Lists.newLinkedList();
// NOTE: argMap is a linked map so we get the parameters in the
// order that they were declared.
for (Entry<String, Node> entry : argMap.entrySet()) {
String name = entry.getKey();
if (namesToAlias.contains(name)) {
Node newValue = entry.getValue().cloneTree();
Node newNode = NodeUtil.newVarNode(name, newValue);
newVars.add(0, newNode);
// Remove the parameter from the list to replace.
newArgMap.remove(name);
}
}
// Inline the arguments.
Node result = FunctionArgumentInjector.inject(
fnTemplateRoot, null, newArgMap);
Preconditions.checkState(result == fnTemplateRoot);
// Now that the names have been replaced, add the new aliases for
// the old names.
for (Node n : newVars) {
fnTemplateRoot.addChildToFront(n);
}
return result;
}
}
/**
* Convert returns to assignments and breaks, as needed.
* For example, with a lableName of 'foo':
* {
* return a;
* }
* becomes:
* foo: {
* a;
* break foo;
* }
* or
* foo: {
* resultName = a;
* break foo;
* }
*
* @param resultMustBeSet Whether the result must always be set to a value.
* @return The node containing the transformed block, this may be different
* than the passed in node 'block'.
*/
private static Node replaceReturns(
Node block, String resultName, String labelName,
boolean resultMustBeSet) {
Preconditions.checkNotNull(block);
Preconditions.checkNotNull(labelName);
Node root = block;
boolean hasReturnAtExit = false;
int returnCount = NodeUtil.getNodeTypeReferenceCount(block, Token.RETURN);
if (returnCount > 0) {
hasReturnAtExit
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> = hasReturnAtExit(block);
// TODO(johnlenz): Simpler not to special case this,
// and let it be optimized later.
if (hasReturnAtExit) {
convertLastReturnToStatement(block, resultName);
returnCount--;
}
if (returnCount > 0) {
// A label and breaks are needed.
// Add the breaks
replaceReturnWithBreak(block, null, resultName, labelName);
// Add label
Node label = new Node(Token.LABEL);
Node name = Node.newString(Token.NAME, labelName);
label.addChildToFront(name);
label.addChildToBack(block);
Node newRoot = new Node(Token.BLOCK);
newRoot.addChildrenToBack(label);
// The label is now the root.
root = newRoot;
}
}
// If there wasn't an return at the end of the function block, and we need
// a result, add one to the block.
if (resultMustBeSet && !hasReturnAtExit && resultName != null) {
addDummyAssignment(block, resultName);
}
return root;
}
/**********************************************************************
* Functions following here are general node transformation functions
**********************************************************************/
/**
* Example:
* a = (void) 0;
*/
private static void addDummyAssignment(Node node, String resultName) {
Preconditions.checkArgument(node.getType() == Token.BLOCK);
// A result is needed create a dummy value.
Node retVal = NodeUtil.newUndefinedNode();
Node resultNode = createAssignStatementNode(resultName, retVal);
node.addChildrenToBack(resultNode);
}
/**
* Replace the 'return' statement with its child expression.
* "return foo()" becomes "foo()" or "resultName = foo()"
* "return" is removed or becomes "resultName = void 0".
*
* @param block
* @param resultName
*/
private static void convertLastReturnToStatement(
Node block, String resultName) {
Node ret = block.getLastChild();
Preconditions.checkArgument(ret.getType() == Token.RETURN);
Node resultNode = getReplacementReturnStatement(ret, resultName);
if (resultNode == null)
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(); break;}"
* "return" becomes {break;} or "{resultName = void 0;break;}".
*/
private static Node replaceReturnWithBreak(Node current, Node parent,
String resultName, String labelName) {
if (current.getType() == Token.FUNCTION
|| current.getType() == Token.EXPR_RESULT) {
// Don't recurse into functions definitions, and expressions can't
// contain RETURN nodes.
return current;
}
if (current.getType() == Token.RETURN) {
Preconditions.checkState(NodeUtil.isStatementBlock(parent));
Node resultNode = getReplacementReturnStatement(current, resultName);
Node name = Node.newString(Token.NAME, labelName);
Node breakNode = new Node(Token.BREAK, name);
// Replace the node in parent, and reset current to the first new child.
parent.replaceChild(current, breakNode);
if (resultNode != null) {
parent.addChildBefore(resultNode, breakNode);
}
current = breakNode;
} else {
for (Node c = current.getFirstChild(); c != null; c = c.getNext()) {
// c may be replaced.
c = replaceReturnWithBreak(c, current, resultName, labelName);
}
}
return current;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import java.nio.charset.Charset;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*
*/
class TypedCodeGenerator extends CodeGenerator {
TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) {
super(consumer, outputCharset, true);
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent.getType() == Token.BLOCK || parent.getType() == Token.SCRIPT) {
if (n.getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n));
} else if (n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN) {
Node rhs = n.getFirstChild().getFirstChild();
add(getTypeAnnotation(rhs));
} else if (n.getType() == Token.VAR
&& n.getFirstChild().getFirstChild() != null
&& n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n.getFirstChild().getFirstChild()));
}
}
super.add(n, context);
}
private String getTypeAnnotation(Node node) {
JSType type = node.getJSType();
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> {
this.rootRenamer = new ContextualRenamer();
}
MakeDeclaredNamesUnique(Renamer renamer) {
this.rootRenamer = renamer;
}
static CompilerPass getContextualRenameInverter(AbstractCompiler compiler) {
return new ContextualRenameInverter(compiler);
}
@Override
public void enterScope(NodeTraversal t) {
Node declarationRoot = t.getScopeRoot();
Renamer renamer;
if (nameStack.isEmpty()) {
// If the contextual renamer is being used the starting context can not
// be a function.
Preconditions.checkState(
declarationRoot.getType() != Token.FUNCTION ||
!(rootRenamer instanceof ContextualRenamer));
Preconditions.checkState(t.inGlobalScope());
renamer = rootRenamer;
} else {
renamer = nameStack.peek().forChildScope();
}
if (declarationRoot.getType() == Token.FUNCTION) {
// Add the function parameters
Node fnParams = declarationRoot.getFirstChild().getNext();
for (Node c = fnParams.getFirstChild(); c != null; c = c.getNext()) {
String name = c.getString();
renamer.addDeclaredName(name);
}
// Add the function body declarations
Node functionBody = declarationRoot.getLastChild();
findDeclaredNames(functionBody, null, renamer);
} else {
// Add the block declarations
findDeclaredNames(declarationRoot, null, renamer);
}
nameStack.push(renamer);
}
@Override
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
nameStack.pop();
}
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.FUNCTION:
{
// Add recursive function name, if needed.
// NOTE: "enterScope" is called after we need to pick up this name.
Renamer renamer = nameStack.peek().forChildScope();
// If needed, add the function recursive name.
String name = n.getFirstChild().getString();
if (name != null && !name.isEmpty() && parent != null
&& !NodeUtil.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>isFunctionDeclaration(n)) {
renamer.addDeclaredName(name);
}
nameStack.push(renamer);
}
break;
case Token.CATCH:
{
Renamer renamer = nameStack.peek().forChildScope();
String name = n.getFirstChild().getString();
renamer.addDeclaredName(name);
nameStack.push(renamer);
}
break;
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
String newName = getReplacementName(n.getString());
if (newName != null) {
Renamer renamer = nameStack.peek();
if (renamer.stripConstIfReplaced()) {
// TODO(johnlenz): Do we need to do anything about the javadoc?
n.removeProp(Node.IS_CONSTANT_NAME);
}
n.setString(newName);
t.getCompiler().reportCodeChange();
}
break;
case Token.FUNCTION:
// Remove function recursive name (if any).
nameStack.pop();
break;
case Token.CATCH:
// Remove catch except name from the stack of names.
nameStack.pop();
break;
}
}
/**
* Walks the stack of name maps and finds the replacement name for the
* current scope.
*/
private String getReplacementName(String oldName) {
for (Renamer names : nameStack) {
String newName = names.getReplacementName(oldName);
if (newName != null) {
return newName;
}
}
return null;
}
/**
* Traverses the current scope and collects declared names. Does not
* decent into functions or add CATCH exceptions.
*/
private void findDeclaredNames(Node n, Node parent, Renamer renamer) {
// Do a shallow traversal, so don't traverse into function declarations,
// except for the name of the function itself.
if (parent == null
|| parent.getType() != Token.FUNCTION
|| n == parent.getFirstChild()) {
if (NodeUtil.isVarDeclaration(n)) {
renamer.addDeclaredName(n.getString());
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
/**
* Prepare a set for the new scope.
*/
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
referenceStack.push(referencedNames);
referencedNames = Sets.newHashSet();
}
/**
* Rename vars for the current scope, and merge any referenced
* names into the parent scope reference set.
*/
public void exitScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
handleScopeVar(v);
}
// Merge any names that were referenced but not declared in the current
// scope.
Set<String> current = referencedNames;
referencedNames = referenceStack.pop();
// If there isn't anything left in the stack we will be going into the
// global scope: don't try to build a set of referenced names for the
// global scope.
if (!referenceStack.isEmpty()) {
referencedNames.addAll(current);
}
}
/**
* For the Var declared in the current scope determine if it is possible
* to revert the name to its orginal form without conflicting with other
* values.
*/
void handleScopeVar(Var v) {
String name = v.getName();
if (containsSeparator(name)) {
String newName = getOrginalName(name);
// Check if the new name is valid and if it would cause conflicts.
if (TokenStream.isJSIdentifier(newName) &&
!referencedNames.contains(newName) &&
!newName.equals(ARGUMENTS)) {
referencedNames.remove(name);
// Adding a reference to the new name to prevent either the parent
// scopes or the current scope renaming another var to this new name.
referencedNames.add(newName);
List<Node> references = nameMap.get(name);
Preconditions.checkState(references != null);
for (Node n : references) {
Preconditions.checkState(n.getType() == Token.NAME);
n.setString(newName);
}
compiler.reportCodeChange();
}
nameMap.remove(name);
}
}
@Override
public
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>ARGUMENTS)) {
if (global) {
reserveName(name);
} else {
// It hasn't been declared locally yet, so increment the count.
if (!declarations.containsKey(name)) {
int id = incrementNameCount(name);
String newName = null;
if (id != 0) {
newName = getUniqueName(name, id);
}
declarations.put(name, newName);
}
}
}
}
@Override
public String getReplacementName(String oldName) {
return declarations.get(oldName);
}
/**
* Given a name and the associated id, create a new unique name.
*/
private String getUniqueName(String name, int id) {
return name + UNIQUE_ID_SEPARATOR + id;
}
private void reserveName(String name) {
nameUsage.setCount(name, 0, 1);
}
private int incrementNameCount(String name) {
return nameUsage.add(name, 1);
}
@Override
public boolean stripConstIfReplaced() {
return false;
}
}
/**
* Rename every declared name to be unique. Typically this would be used
* when injecting code to insure that names do not conflict with existing
* names.
*
* Used by the FunctionInjector
* @see FunctionInjector
*/
static class InlineRenamer implements Renamer {
private final Map<String, String> declarations = Maps.newHashMap();
private final Supplier<String> uniqueIdSupplier;
private final String idPrefix;
private final boolean removeConstness;
InlineRenamer(
Supplier<String> uniqueIdSupplier,
String idPrefix,
boolean removeConstness) {
this.uniqueIdSupplier = uniqueIdSupplier;
// To ensure that the id does not conflict with the id from the
// ContextualRenamer some prefix is needed.
Preconditions.checkArgument(!idPrefix.isEmpty());
this.idPrefix = idPrefix;
this.removeConstness = removeConstness;
}
@Override
public void addDeclaredName(String name) {
Preconditions.checkState(!name.equals(ARGUMENTS));
if (!declarations.containsKey(name)) {
declarations.put(name, getUniqueName(name));
}
}
private String getUniqueName
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR ||
parent.getType() == Token.NEW) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
JSError.make(t, n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
JSError.make(t, n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.getType() == Token.NEW) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
JSError.make(t, n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
JSError.make(t, n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.getType() == Token.ASSIGN &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecationInfo(implicitProto);
}
}
return null;
}
/**
* Returns the deprecation reason for the property if it is marked
* as being deprecated. Returns empty string if the property is deprecated
* but no reason was given. Returns null if the property is not deprecated.
*/
private static String getPropertyDeprecationInfo(ObjectType type,
String prop) {
JSDocInfo info = type.getOwnPropertyJSDocInfo(prop);
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType implicitProto = type.getImplicitPrototype();
if
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> inlining, so we do this before
// the main optimization loop.
if (options.collapseProperties) {
passes.add(collapseProperties);
}
// Tighten types based on actual usage.
if (options.tightenTypes) {
passes.add(tightenTypesBuilder);
}
// Property disambiguation should only run once and needs to be done
// soon after type checking, both so that it can make use of type
// information and so that other passes can take advantage of the renamed
// properties.
if (options.disambiguateProperties) {
passes.add(disambiguateProperties);
}
if (options.computeFunctionSideEffects) {
passes.add(markPureFunctions);
} else if (options.markNoSideEffectCalls) {
// TODO(user) The properties that this pass adds to CALL and NEW
// AST nodes increase the AST's in-memory size. Given that we are
// already running close to our memory limits, we could run into
// trouble if we end up using the @nosideeffects annotation a lot
// or compute @nosideeffects annotations by looking at function
// bodies. It should be easy to propagate @nosideeffects
// annotations as part of passes that depend on this property and
// store the result outside the AST (which would allow garbage
// collection once the pass is done).
passes.add(markNoSideEffectCalls);
}
if (options.chainCalls) {
passes.add(chainCalls);
}
// Constant checking must be done after property collapsing because
// property collapsing can introduce new constants (e.g. enum values).
if (options.inlineConstantVars) {
passes.add(checkConsts);
}
// The Caja library adds properties to Object.prototype, which breaks
// most for-in loops. This adds a check to each loop that skips
// any property matching /___$/.
if (options.ignoreCajaProperties) {
passes.add(ignoreCajaProperties);
}
assertAllOneTimePasses(passes);
if (options.smartNameRemoval || options.reportPath != null) {
passes.addAll(getCodeRemovingPasses());
passes.add(smartNamePass);
}
// TODO(user): This forces a
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> first crack at crossModuleCodeMotion
// before devirtualization. Once certain functions are devirtualized,
// it confuses crossModuleCodeMotion ability to recognized that
// it is recursive.
// TODO(user): This is meant for a temporary quick win.
// In the future, we might want to improve our analysis in
// CrossModuleCodeMotion so we don't need to do this.
if (options.crossModuleCodeMotion) {
passes.add(crossModuleCodeMotion);
}
// Method devirtualization benefits from property disambiguiation so
// it should run after that pass but before passes that do
// optimizations based on global names (like cross module code motion
// and inline functions). Smart Name Removal does better if run before
// this pass.
if (options.devirtualizePrototypeMethods) {
passes.add(devirtualizePrototypeMethods);
}
if (options.customPasses != null) {
passes.add(getCustomPasses(
CustomPassExecutionTime.BEFORE_OPTIMIZATION_LOOP));
}
passes.add(createEmptyPass("beforeMainOptimizations"));
passes.addAll(getMainOptimizationLoop());
passes.add(createEmptyPass("beforeModuleMotion"));
if (options.crossModuleCodeMotion) {
passes.add(crossModuleCodeMotion);
}
if (options.crossModuleMethodMotion) {
passes.add(crossModuleMethodMotion);
}
passes.add(createEmptyPass("afterModuleMotion"));
// Some optimizations belong outside the loop because running them more
// than once would either have no benefit or be incorrect.
if (options.customPasses != null) {
passes.add(getCustomPasses(
CustomPassExecutionTime.AFTER_OPTIMIZATION_LOOP));
}
if (options.flowSensitiveInlineVariables) {
passes.add(flowSensitiveInlineVariables);
// After inlining some of the variable uses, some variables are unused.
// Re-run remove unused vars to clean it up.
if (options.removeUnusedVars) {
passes.add(removeUnusedVars);
}
}
if (options.collapseAnonymousFunctions) {
passes.add(collapseAnonymousFunctions);
}
// Move functions before extracting prototype member declarations.
if (options.moveFunctionDeclarations) {
passes
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
}
passes.add(denormalize);
if (options.instrumentationTemplate != null) {
passes.add(instrumentFunctions);
}
if (options.variableRenaming != VariableRenamingPolicy.ALL) {
// If we're leaving some (or all) variables with their old names,
// then we need to undo any of the markers we added for distinguishing
// local variables ("$$1").
passes.add(invertContextualRenaming);
}
if (options.variableRenaming != VariableRenamingPolicy.OFF) {
passes.add(renameVars);
}
// This pass should run after names stop changing.
if (options.processObjectPropertyString) {
passes.add(objectPropertyStringPostprocess);
}
if (options.labelRenaming) {
passes.add(renameLabels);
}
if (options.anonymousFunctionNaming ==
AnonymousFunctionNamingPolicy.UNMAPPED) {
passes.add(nameUnmappedAnonymousFunctions);
}
// Safety check
if (options.checkSymbols) {
passes.add(sanityCheckVars);
}
return passes;
}
/** Creates the passes for the main optimization loop. */
private List<PassFactory> getMainOptimizationLoop() {
List<PassFactory> passes = Lists.newArrayList();
if (options.inlineGetters) {
passes.add(inlineGetters);
}
passes.addAll(getCodeRemovingPasses());
if (options.inlineFunctions || options.inlineLocalFunctions) {
passes.add(inlineFunctions);
}
if (options.removeUnusedVars) {
if (options.deadAssignmentElimination) {
passes.add(deadAssignmentsElimination);
}
passes.add(removeUnusedVars);
}
assertAllLoopablePasses(passes);
return passes;
}
/** Creates several passes aimed at removing code. */
private List<PassFactory> getCodeRemovingPasses() {
List<PassFactory> passes = Lists.newArrayList();
if (options.inlineVariables || options.inlineLocalVariables) {
passes.add(inlineVariables);
} else if (options.inlineConstantVars) {
passes.add(inlineConstants);
}
if (options.removeConstantExpressions) {
passes.add(removeConstantExpressions);
}
if
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> (options.foldConstants) {
// These used to be one pass.
passes.add(minimizeExitPoints);
passes.add(foldConstants);
}
if (options.removeDeadCode) {
passes.add(removeUnreachableCode);
}
if (options.removeUnusedPrototypeProperties) {
passes.add(removeUnusedPrototypeProperties);
}
assertAllLoopablePasses(passes);
return passes;
}
/**
* Checks for code that is probably wrong (such as stray expressions).
*/
// TODO(bolinfest): Write a CompilerPass for this.
final PassFactory suspiciousCode =
new PassFactory("suspiciousCode", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
List<Callback> sharedCallbacks = Lists.newArrayList();
sharedCallbacks.add(new CheckAccidentalSemicolon(CheckLevel.WARNING));
sharedCallbacks.add(new CheckSideEffects(CheckLevel.WARNING));
if (options.checkGlobalThisLevel.isOn()) {
sharedCallbacks.add(
new CheckGlobalThis(compiler, options.checkGlobalThisLevel));
}
return combineChecks(compiler, sharedCallbacks);
}
};
/** Verify that all the passes are one-time passes. */
private void assertAllOneTimePasses(List<PassFactory> passes) {
for (PassFactory pass : passes) {
Preconditions.checkState(pass.isOneTimePass());
}
}
/** Verify that all the passes are multi-run passes. */
private void assertAllLoopablePasses(List<PassFactory> passes) {
for (PassFactory pass : passes) {
Preconditions.checkState(!pass.isOneTimePass());
}
}
/** Checks for validity of the control structures. */
private final PassFactory checkControlStructures =
new PassFactory("checkControlStructures", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new ControlStructureCheck(compiler);
}
};
/** Checks that all constructed classes are goog.require()d. */
private final PassFactory checkRequires =
new PassFactory("checkRequires", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CheckRequiresForConstructors(compiler, options.checkRequires
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>);
}
};
/** Makes sure @constructor is paired with goog.provides(). */
private final PassFactory checkProvides =
new PassFactory("checkProvides", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CheckProvides(compiler, options.checkProvides);
}
};
private static final DiagnosticType GENERATE_EXPORTS_ERROR =
DiagnosticType.error(
"JSC_GENERATE_EXPORTS_ERROR",
"Exports can only be generated if export symbol/property " +
"functions are set.");
/** Generates exports for @export annotations. */
private final PassFactory generateExports =
new PassFactory("generateExports", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
CodingConvention convention = compiler.getCodingConvention();
if (convention.getExportSymbolFunction() != null &&
convention.getExportPropertyFunction() != null) {
return new GenerateExports(compiler,
convention.getExportSymbolFunction(),
convention.getExportPropertyFunction());
} else {
return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
}
}
};
/** Generates exports for functions associated with JSUnit. */
private final PassFactory exportTestFunctions =
new PassFactory("exportTestFunctions", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
CodingConvention convention = compiler.getCodingConvention();
if (convention.getExportSymbolFunction() != null) {
return new ExportTestFunctions(compiler,
convention.getExportSymbolFunction());
} else {
return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
}
}
};
/** Raw exports processing pass. */
final PassFactory gatherRawExports =
new PassFactory("gatherRawExports", false) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
final GatherRawExports pass = new GatherRawExports(
compiler);
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
pass.process(externs, root);
if (exportedNames == null) {
exportedNames = Sets.newHashSet();
}
exportedNames.addAll(pass.getExportedVariableNames());
}
};
}
};
/** Closure pre
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>-processing pass. */
@SuppressWarnings("deprecation")
final PassFactory closurePrimitives =
new PassFactory("processProvidesAndRequires", false) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
final ProcessClosurePrimitives pass = new ProcessClosurePrimitives(
compiler,
options.brokenClosureRequiresLevel,
options.rewriteNewDateGoogNow);
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
pass.process(externs, root);
exportedNames = pass.getExportedVariableNames();
}
};
}
};
/** Checks that CSS class names are wrapped in goog.getCssName */
private final PassFactory closureCheckGetCssName =
new PassFactory("checkMissingGetCssName", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
String blacklist = options.checkMissingGetCssNameBlacklist;
Preconditions.checkState(blacklist != null && !blacklist.isEmpty(),
"Not checking use of goog.getCssName because of empty blacklist.");
return new CheckMissingGetCssName(
compiler, options.checkMissingGetCssNameLevel, blacklist);
}
};
/**
* Processes goog.getCssName. The cssRenamingMap is used to lookup
* replacement values for the classnames. If null, the raw class names are
* inlined.
*/
private final PassFactory closureReplaceGetCssName =
new PassFactory("renameCssNames", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
Map<String, Integer> newCssNames = null;
if (options.gatherCssNames) {
newCssNames = Maps.newHashMap();
}
(new ReplaceCssNames(compiler, newCssNames)).process(
externs, jsRoot);
cssNames = newCssNames;
}
};
}
};
/**
* Creates synthetic blocks to prevent FoldConstants from moving code
* past markers in the source.
*/
private final PassFactory createSyntheticBlocks =
new PassFactory("createSyntheticBlocks", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CreateSynt
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(AbstractCompiler compiler) {
return new GlobalTypeResolver(compiler);
}
};
/** Rusn type inference. */
private final PassFactory inferTypes =
new PassFactory("inferTypes", false) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(typedScopeCreator);
makeTypeInference(compiler).process(externs, root);
}
};
}
};
/** Checks type usage */
private final PassFactory checkTypes =
new PassFactory("checkTypes", false) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(typedScopeCreator);
TypeCheck check = makeTypeCheck(compiler);
check.process(externs, root);
compiler.getErrorManager().setTypedPercent(check.getTypedPercent());
}
};
}
};
/**
* Checks possible execution paths of the program for problems: missing return
* statements and dead code.
*/
private final PassFactory checkControlFlow =
new PassFactory("checkControlFlow", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
List<Callback> callbacks = Lists.newArrayList();
if (options.checkUnreachableCode.isOn()) {
callbacks.add(
new CheckUnreachableCode(compiler, options.checkUnreachableCode));
}
if (options.checkMissingReturn.isOn() && options.checkTypes) {
callbacks.add(
new CheckMissingReturn(compiler, options.checkMissingReturn));
}
return combineChecks(compiler, callbacks);
}
};
/** Checks access controls. Depends on type-inference. */
private final PassFactory checkAccessControls =
new PassFactory("checkAccessControls", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CheckAccessControls(compiler);
}
};
/** Executes the given callbacks with a {@link CombinedCompilerPass}. */
private static CompilerPass combineChecks(AbstractCompiler compiler,
List<Callback
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>> callbacks) {
Preconditions.checkArgument(callbacks.size() > 0);
Callback[] array = callbacks.toArray(new Callback[callbacks.size()]);
return new CombinedCompilerPass(compiler, array);
}
/** A compiler pass that resolves types in the global scope. */
private class GlobalTypeResolver implements CompilerPass {
private final AbstractCompiler compiler;
GlobalTypeResolver(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (topScope == null) {
typedScopeCreator =
new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = typedScopeCreator.createScope(root.getParent(), null);
} else {
compiler.getTypeRegistry().resolveTypesInScope(topScope);
}
}
}
/** Checks global name usage. */
private final PassFactory checkGlobalNames =
new PassFactory("Check names", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
// Create a global namespace for analysis by check passes.
// Note that this class does all heavy computation lazily,
// so it's OK to create it here.
namespaceForChecks = new GlobalNamespace(compiler, jsRoot);
new CheckGlobalNames(compiler, options.checkGlobalNamesLevel)
.injectNamespace(namespaceForChecks).process(externs, jsRoot);
}
};
}
};
/** Checks for properties that are not read or written */
private final PassFactory checkSuspiciousProperties =
new PassFactory("checkSuspiciousProperties", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new SuspiciousPropertiesCheck(
compiler,
options.checkUndefinedProperties,
options.checkUnusedPropertiesEarly ?
CheckLevel.WARNING : CheckLevel.OFF);
}
};
/** Checks that the code is ES5 or Caja compliant. */
private final PassFactory checkStrictMode =
new PassFactory("checkStrictMode", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new StrictModeCheck(compiler,
!options.checkSymbols, // don't check
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
* <ul>
* <li>{@code (number,string)} restricted by {@code number} is
* {@code string}</li>
* <li>{@code (null, EvalError, URIError)} restricted by
* {@code Error} is {@code null}</li>
* </ul>
*
* @param type the supertype of the types to remove from this union type
*/
public JSType getRestrictedUnion(JSType type) {
UnionTypeBuilder restricted = new UnionTypeBuilder(registry);
for (JSType t : alternates) {
if (t.isUnknownType() || !t.isSubtype(type)) {
restricted.addAlternate(t);
}
}
return restricted.build();
}
@Override public String toString() {
StringBuilder result = new StringBuilder();
boolean firstAlternate = true;
result.append("(");
SortedSet<JSType> sorted = new TreeSet<JSType>(ALPHA);
sorted.addAll(alternates);
for (JSType t : sorted) {
if (!firstAlternate) {
result.append("|");
}
result.append(t.toString());
firstAlternate = false;
}
result.append(")");
return result.toString();
}
@Override
public boolean isSubtype(JSType that) {
for (JSType element : alternates) {
if (!element.isSubtype(that)) {
return false;
}
}
return true;
}
@Override
public JSType getRestrictedTypeGivenToBooleanOutcome(boolean outcome) {
// gather elements after restriction
UnionTypeBuilder restricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
restricted.addAlternate(
element.getRestrictedTypeGivenToBooleanOutcome(outcome));
}
return restricted.build();
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
BooleanLiteralSet literals = BooleanLiteralSet.EMPTY;
for (JSType element : alternates) {
literals = literals.union(element.getPossibleToBooleanOutcomes());
if (literals == BooleanLiteralSet.BOTH) {
break;
}
}
return literals;
}
@Override
public TypePair getTypesUnderEquality(JSType that) {
UnionTypeBuilder thisRestricted = new
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> {
JSType newAlternate = alternate.resolve(t, scope);
changed |= (alternate != newAlternate);
resolvedTypes.add(alternate);
}
if (changed) {
Set<JSType> newAlternates = resolvedTypes.build();
Preconditions.checkState(newAlternates.hashCode() == this.hashcode);
alternates = newAlternates;
}
return this;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>;
case JAVA_DISPATCH:
if (!jsdocBuilder.recordJavaDispatch()) {
parser.addWarning("msg.jsdoc.javadispatch",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTENDS:
case IMPLEMENTS:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
boolean matchingRc = false;
if (token == JsDocToken.LC) {
token = next();
matchingRc = true;
}
if (token == JsDocToken.STRING) {
Node typeNode = parseAndRecordTypeNameNode(
token, lineno, charno, matchingRc);
lineno = stream.getLineno();
charno = stream.getCharno();
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && !matchingRc) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
type = createJSTypeExpression(typeNode);
if (annotation == Annotation.EXTENDS) {
if (!jsdocBuilder.recordBaseType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
} else {
Preconditions.checkState(
annotation == Annotation.IMPLEMENTS);
if (!jsdocBuilder.recordImplementedInterface(type)) {
parser.addWarning("msg.jsdoc.implements.duplicate",
lineno, charno);
}
}
token = next();
if (matchingRc) {
if (token != JsDocToken.RC) {
parser.addWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
} else if (token != JsDocToken.EOL &&
token != JsDocToken.EOF && token != JsDocToken.EOC) {
parser.addWarning("msg.end.annotation.expected",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addWarning("msg.no.type.name", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> found or null if none.
*/
private Node parseAndRecordParamTypeNode(JsDocToken token) {
Preconditions.checkArgument(token == JsDocToken.LC);
int lineno = stream.getLineno();
int startCharno = stream.getCharno();
Node typeNode = parseParamTypeExpressionAnnotation(token);
int endCharno = stream.getCharno();
jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno,
true);
return typeNode;
}
/**
* Looks for a parameter type expression at the current token and if found,
* returns it. Note that this method consumes input.
*
* @param token The current token.
* @param lineno The line of the type expression.
* @param startCharno The starting character position of the type expression.
* @param matchingLC Whether the type expression starts with a "{".
* @param onlyParseSimpleNames If true, only simple type names are parsed
* (via a call to parseTypeNameAnnotation instead of
* parseTypeExpressionAnnotation).
* @return The type expression found or null if none.
*/
private Node parseAndRecordTypeNode(JsDocToken token, int lineno,
int startCharno,
boolean matchingLC,
boolean onlyParseSimpleNames) {
Node typeNode = null;
if (onlyParseSimpleNames) {
typeNode = parseTypeNameAnnotation(token);
} else {
typeNode = parseTypeExpressionAnnotation(token);
}
if (typeNode != null && !matchingLC) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
int endCharno = stream.getCharno();
jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno,
matchingLC);
return typeNode;
}
/**
* Determines whether the given type is a valid {@code @define} type.
*/
// TODO(nicksantos): Move this into a check pass.
private boolean isValidDefineType(Node typeNode) {
JSType type = typeRegistry.createFromTypeNodes(typeNode, "", null);
return !type.isUnknownType() && type.isSubtype(
typeRegistry.getNativeType(JSTypeNative.NUMBER_STRING_
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> = parseTopLevelTypeExpression(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
} else {
return parseTypeExpression(token);
}
}
/**
* ParamTypeExpressionAnnotation :=
* '{' OptionalParameterType '}' |
* '{' TopLevelTypeExpression '}' |
* '{' '...' TopLevelTypeExpression '}'
*
* OptionalParameterType :=
* TopLevelTypeExpression '='
*/
private Node parseParamTypeExpressionAnnotation(JsDocToken token) {
Preconditions.checkArgument(token == JsDocToken.LC);
skipEOLs();
boolean restArg = false;
token = next();
if (token == JsDocToken.ELLIPSIS) {
token = next();
if (token == JsDocToken.RC) {
// EMPTY represents the UNKNOWN type in the Type AST.
return wrapNode(Token.ELLIPSIS, new Node(Token.EMPTY));
}
restArg = true;
}
Node typeNode = parseTopLevelTypeExpression(token);
if (typeNode != null) {
skipEOLs();
if (restArg) {
typeNode = wrapNode(Token.ELLIPSIS, typeNode);
} else if (match(JsDocToken.EQUALS)) {
next();
skipEOLs();
typeNode = wrapNode(Token.EQUALS, typeNode);
}
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
}
/**
* TypeNameAnnotation := TypeName | '{' TypeName '}'
*/
private Node parseTypeNameAnnotation(JsDocToken token) {
if (token == JsDocToken.LC) {
skipEOLs();
Node typeNode = parseTypeName(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Name, ParametersType ')' ResultType
*/
private Node parseFunctionType(JsDocToken token) {
// NOTE(nicksantos): We're not implementing generics at the moment, so
// just throw out TypeParameters.
if (token != JsDocToken.LP) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.lp");
}
Node functionType = newNode(Token.FUNCTION);
Node parameters = null;
skipEOLs();
if (!match(JsDocToken.RP)) {
token = next();
boolean hasParams = true;
if (token == JsDocToken.STRING && "this".equals(stream.getString())) {
if (match(JsDocToken.COLON)) {
next();
skipEOLs();
Node thisType = wrapNode(Token.THIS, parseTypeName(next()));
if (thisType == null) {
return null;
}
functionType.addChildToFront(thisType);
} else {
return reportTypeSyntaxWarning("msg.jsdoc.missing.colon");
}
if (match(JsDocToken.COMMA)) {
next();
skipEOLs();
token = next();
} else {
hasParams = false;
}
}
if (hasParams) {
parameters = parseParametersType(token);
if (parameters == null) {
return null;
}
}
}
if (parameters != null) {
functionType.addChildToBack(parameters);
}
skipEOLs();
if (!match(JsDocToken.RP)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rp");
}
skipEOLs();
Node resultType = parseResultType(next());
if (resultType == null) {
return null;
} else {
functionType.addChildToBack(resultType);
}
return functionType;
}
/**
* ParametersType := RestParameterType | NonRestParametersType
* | NonRestParametersType ',' RestParameterType
* RestParameterType := '...' Identifier
* NonRestParametersType := ParameterType ',' NonRestParametersType
* | ParameterType
* | OptionalParametersType
* OptionalParametersType := OptionalParameterType
* | OptionalParameterType, OptionalParametersType
* OptionalParameterType := ParameterType=
* ParameterType := TypeExpression | Identifier ':'
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> }
paramsType.addChildToBack(paramType);
if (isVarArgs) {
break;
}
} while (match(JsDocToken.COMMA));
}
if (isVarArgs && match(JsDocToken.COMMA)) {
return reportTypeSyntaxWarning("msg.jsdoc.function.varargs");
}
// The right paren will be checked by parseFunctionType
return paramsType;
}
/**
* ResultType := <empty> | ':' void | ':' TypeExpression
*/
private Node parseResultType(JsDocToken token) {
skipEOLs();
if (!match(JsDocToken.COLON)) {
return newNode(Token.EMPTY);
}
token = next();
skipEOLs();
if (match(JsDocToken.STRING) && "void".equals(stream.getString())) {
next();
return newNode(Token.VOID);
} else {
return parseTypeExpression(next());
}
}
/**
* UnionType := '(' TypeUnionList ')'
* TypeUnionList := TypeExpression | TypeExpression '|' TypeUnionList
*
* We've removed the empty union type.
*/
private Node parseUnionType(JsDocToken token) {
return parseUnionTypeWithAlternate(token, null);
}
/**
* Create a new union type, with an alternate that has already been
* parsed. The alternate may be null.
*/
private Node parseUnionTypeWithAlternate(JsDocToken token, Node alternate) {
Node union = newNode(Token.PIPE);
if (alternate != null) {
union.addChildToBack(alternate);
}
Node expr = null;
do {
if (expr != null) {
skipEOLs();
token = next();
Preconditions.checkState(
token == JsDocToken.PIPE || token == JsDocToken.COMMA);
boolean isPipe = token == JsDocToken.PIPE;
if (isPipe && match(JsDocToken.PIPE)) {
// We support double pipes for backwards compatiblity.
next();
}
skipEOLs();
token = next();
}
expr = parseTypeExpression(token);
if (expr == null) {
return null;
}
union.addChildToBack(expr);
// We support commas for backwards compatiblity.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
public void process(Node externs, Node root) {
SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler);
defFinder.process(externs, root);
// Gather the list of function nodes that have @nosideeffect annotations.
// For use by SetNoSideEffectCallProperty.
NodeTraversal.traverse(
compiler, externs, new GatherNoSideEffectFunctions(true));
NodeTraversal.traverse(
compiler, root, new GatherNoSideEffectFunctions(false));
NodeTraversal.traverse(compiler, root,
new SetNoSideEffectCallProperty(defFinder));
}
/**
* Determines if the type of the value of the rhs expression can
* be a function node.
*/
private static boolean definitionTypeContainsFunctionType(Definition def) {
Node rhs = def.getRValue();
if (rhs == null) {
return true;
}
switch (rhs.getType()) {
case Token.ASSIGN:
case Token.AND:
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.FUNCTION:
case Token.HOOK:
case Token.NAME:
case Token.NEW:
case Token.OR:
return true;
default:
return false;
}
}
/**
* Get the value of the @nosideeffects annotation stored in the
* doc info.
*/
private static boolean hasNoSideEffectsAnnotation(Node node) {
JSDocInfo docInfo = node.getJSDocInfo();
return docInfo != null && docInfo.isNoSideEffects();
}
/**
* Gather function nodes that have @nosideeffects annotations.
*/
private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback {
private final boolean inExterns;
GatherNoSideEffectFunctions(boolean inExterns) {
this.inExterns = inExterns;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (!inExterns && hasNoSideEffectsAnnotation(node)) {
traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
if (NodeUtil.isGetProp(node)) {
if (NodeUtil.isExpressionNode(parent) &&
hasNo
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>SideEffectsAnnotation(node)) {
noSideEffectFunctionNames.add(node);
}
} else if (NodeUtil.isFunction(node)) {
// The annotation may attached to the function node, the
// variable declaration or assignment expression.
boolean hasAnnotation = hasNoSideEffectsAnnotation(node);
List<Node> nameNodes = Lists.newArrayList();
nameNodes.add(node.getFirstChild());
Node nameNode = null;
if (NodeUtil.isName(parent)) {
Node gramp = parent.getParent();
if (NodeUtil.isVar(gramp) &&
gramp.hasOneChild() &&
hasNoSideEffectsAnnotation(gramp)) {
hasAnnotation = true;
}
nameNodes.add(parent);
} else if (NodeUtil.isAssign(parent)) {
if (hasNoSideEffectsAnnotation(parent)) {
hasAnnotation = true;
}
nameNodes.add(parent.getFirstChild());
}
if (hasAnnotation) {
noSideEffectFunctionNames.addAll(nameNodes);
}
}
}
}
/**
* Set the no side effects property for CALL and NEW nodes that
* refer to function names that are known to have no side effects.
*/
private class SetNoSideEffectCallProperty extends AbstractPostOrderCallback {
private final SimpleDefinitionFinder defFinder;
SetNoSideEffectCallProperty(SimpleDefinitionFinder defFinder) {
this.defFinder = defFinder;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (!NodeUtil.isCall(node) && !NodeUtil.isNew(node)) {
return;
}
Collection<Definition> definitions =
defFinder.getDefinitionsReferencedAt(node.getFirstChild());
if (definitions == null) {
return;
}
for (Definition def : definitions) {
Node lValue = def.getLValue();
Preconditions.checkNotNull(lValue);
if (!noSideEffectFunctionNames.contains(lValue) &&
definitionTypeContainsFunctionType(def)) {
return;
}
}
node.setIsNoSideEffectsCall();
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(),
traversal.inGlobalScope(),
inExterns));
}
}
}
}
private class UseSiteGatheringCallback extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
Collection<Definition> defs = getDefinitionsReferencedAt(node);
if (defs == null) {
return;
}
Definition first = defs.iterator().next();
String name = getSimplifiedName(first.getLValue());
Preconditions.checkNotNull(name);
nameUseSiteMultimap.put(
name,
new UseSite(node, traversal.getModule()));
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> values in the graph.
*/
public abstract List<GraphEdge<N, E>> getEdges(N n1, N n2);
/**
* Checks whether the node exists in the graph ({@link #createNode(Object)}
* has been called with that value).
*
* @param n Node.
* @return <code>true</code> if it exist.
*/
public final boolean hasNode(N n) {
return getNode(n) != null;
}
/**
* Checks whether two nodes in the graph are connected.
*
* @param n1 Node 1.
* @param n2 Node 2.
* @return <code>true</code> if the two nodes are connected.
*/
public abstract boolean isConnected(N n1, N n2);
public final void clearNodeAnnotations() {
for (GraphNode<N, E> n : getNodes()) {
n.setAnnotation(null);
}
}
/** Makes each edge's annotation null. */
public final void clearEdgeAnnotations() {
for (GraphEdge<N, E> e : getEdges()) {
e.setAnnotation(null);
}
}
/**
* Pushes nodes' annotation values. Restored with
* {@link #popNodeAnnotations()}. Nodes' annotation values are cleared.
*/
public final void pushNodeAnnotations() {
if (nodeAnnotationStack == null) {
nodeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(nodeAnnotationStack, getNodes());
}
/**
* Restores nodes' annotation values to state before last
* {@link #pushNodeAnnotations()}.
*/
public final void popNodeAnnotations() {
Preconditions.checkNotNull(nodeAnnotationStack,
"Popping node annotations without pushing.");
popAnnotations(nodeAnnotationStack);
}
/**
* Pushes edges' annotation values. Restored with
* {@link #popEdgeAnnotations()}. Edges' annotation values are cleared.
*/
public final void pushEdgeAnnotations() {
if (edgeAnnotationStack == null) {
edgeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(edgeAnnotationStack, getEdges());
}
/**
* Restores edges' annotation values to state before last
* {@link #pushEdgeAnnotations()}.
*/
public final void popEdgeAnnotations
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>() {
Preconditions.checkNotNull(edgeAnnotationStack,
"Popping edge annotations without pushing.");
popAnnotations(edgeAnnotationStack);
}
/**
* A generic edge.
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public interface GraphEdge<N, E> extends Annotatable {
/**
* Retrieves the edge's value.
*
* @return The value.
*/
E getValue();
GraphNode<N, E> getNodeA();
GraphNode<N, E> getNodeB();
}
/**
* A simple implementation of SubGraph that calculates adjacency by iterating
* over a node's neighbors.
*/
class SimpleSubGraph<N, E> implements SubGraph<N, E> {
private Graph<N, E> graph;
private List<GraphNode<N, E>> nodes = Lists.newArrayList();
SimpleSubGraph(Graph<N, E> graph) {
this.graph = graph;
}
public boolean isIndependentOf(N value) {
GraphNode<N, E> node = graph.getNode(value);
for (GraphNode<N, E> n : nodes) {
if (graph.getNeighborNodes(n.getValue()).contains(node)) {
return false;
}
}
return true;
}
public void addNode(N value) {
if (!graph.hasNode(value)) {
throw new IllegalArgumentException(value + " does not exist in graph");
}
nodes.add(graph.getNode(value));
}
}
/**
* Pushes a new list on stack and stores nodes annotations in the new list.
* Clears objects' annotations as well.
*/
private static void pushAnnotations(
Deque<GraphAnnotationState> stack,
Collection<? extends Annotatable> haveAnnotations) {
stack.push(new GraphAnnotationState(haveAnnotations.size()));
for (Annotatable h : haveAnnotations) {
stack.peek().add(new AnnotationState(h, h.getAnnotation()));
h.setAnnotation(null);
}
}
/**
* Restores the node annotations on the top of stack and pops stack.
*/
private static void popAnnotations(Deque<GraphAnnotationState> stack) {
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* Methods necessary for partially or full decomposing an expression. Initially
* this is intended to expanded the locations were inlining can occur, but has
* other uses as well.
*
* For example:
* var x = y() + z();
*
* Becomes:
* var a = y();
* var b = z();
* x = a + b;
*
* @author johnlenz@google.com (John Lenz)
*/
class ExpressionDecomposer {
/**
* @see #canExposeExpression
*/
enum DecompositionType {
UNDECOMPOSABLE,
MOVABLE,
DECOMPOSABLE
}
private final AbstractCompiler compiler;
private final Supplier<String> safeNameIdSupplier;
private final Set<String> knownConstants;
public ExpressionDecomposer(
AbstractCompiler compiler,
Supplier<String> safeNameIdSupplier,
Set<String> constNames) {
Preconditions.checkNotNull(compiler);
Preconditions.checkNotNull(safeNameIdSupplier);
Preconditions.checkNotNull(constNames);
this.compiler = compiler;
this.safeNameIdSupplier = safeNameIdSupplier;
this.knownConstants = constNames;
}
// An arbitrary limit to prevent catch infinite
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> recursion.
private static final int MAX_INTERATIONS = 100;
/**
* If required, rewrite the statement containing the expression.
* @param expression The expression to be exposed.
* @see #canExposeExpression
*/
void maybeDecomposeExpression(Node expression) {
// If the expression needs to exposed.
int i = 0;
while (DecompositionType.DECOMPOSABLE == canExposeExpression(expression)) {
exposeExpression(expression);
if (i > MAX_INTERATIONS) {
throw new IllegalStateException(
"DecomposeExpression depth exceeded on :\n" +
expression.toStringTree());
}
}
}
/**
* Perform any rewriting necessary so that the specified expression
* is movable. This is a partial expression decomposition.
* @see #canExposeExpression
*/
void exposeExpression(Node expression) {
Node expressionRoot = findExpressionRoot(expression);
Preconditions.checkState(expressionRoot != null);
exposeExpression(expressionRoot, expression);
compiler.reportCodeChange();
}
// TODO(johnlenz): This is not currently used by the function inliner,
// as moving the call out of the expression before the actual function
// results in additional variables being introduced. As the variable
// inliner is improved, this might be a viable option.
/**
* Extract the specified expression from its parent expression.
* @see #canExposeExpression
*/
void moveExpression(Node expression) {
String resultName = getTempValueName(); // Should this be constant?
Node injectionPoint = findInjectionPoint(expression);
Preconditions.checkNotNull(injectionPoint);
Node injectionPointParent = injectionPoint.getParent();
Preconditions.checkNotNull(injectionPointParent);
Preconditions.checkState(NodeUtil.isStatementBlock(injectionPointParent));
// Replace the expression with a reference to the new name.
Node expressionParent = expression.getParent();
expressionParent.replaceChild(
expression, Node.newString(Token.NAME, resultName));
// Re-add the expression at the appropriate place.
Node newExpressionRoot = NodeUtil.newVarNode(resultName, expression);
injectionPointParent.addChildBefore(newExpressionRoot, injectionPoint);
compiler.reportCodeChange();
}
/**
* Rewrite the expression such that the sub-expression is
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> in a movable
* expression statement while maintaining evaluation order.
*
* Two types of subexpressions are extracted from the source expression:
* 1) subexpressions with side-effects.
* 2) conditional expressions, that contain the call, which are transformed
* into IF statements.
*
* The following terms are used:
* expressionRoot: The top level node before which the any extracted
* expressions should be placed before.
* nonconditionalExpr: The node that will be extracted either expres.
*
*/
private void exposeExpression(Node expressionRoot, Node subExpression) {
Node nonconditionalExpr = findNonconditionalParent(
subExpression, expressionRoot);
// Before extraction, record whether there are side-effect
boolean hasFollowingSideEffects = NodeUtil.mayHaveSideEffects(
nonconditionalExpr);
Node exprInjectionPoint = findInjectionPoint(nonconditionalExpr);
DecompositionState state = new DecompositionState();
state.sideEffects = hasFollowingSideEffects;
state.extractBeforeStatement = exprInjectionPoint;
// Extract expressions in the reverse order of their evaluation.
for (Node child = nonconditionalExpr, parent = child.getParent();
parent != expressionRoot;
child = parent, parent = child.getParent()) {
int parentType = parent.getType();
Preconditions.checkState(
!isConditionalOp(parent) || child == parent.getFirstChild());
if (parentType == Token.ASSIGN) {
if (isSafeAssign(parent, state.sideEffects)) {
// It is always safe to inline "foo()" for expressions such as
// "a = b = c = foo();"
// As the assignment is unaffected by side effect of "foo()"
// and the names assigned-to can not influence the state before
// the call to foo.
//
// This is not true of more complex LHS values, such as
// a.x = foo();
// next().x = foo();
// in these cases the checks below are necessary.
} else {
// Alias "next()" in "next().foo"
Node left = parent.getFirstChild();
int type = left.getType();
if (left != child) {
Preconditions.checkState(NodeUtil.isGet(left));
if (type == Token.GETELEM) {
decompose
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Point, boolean needResult) {
Node parent = expr.getParent();
String tempName = getTempValueName();
// Break down the conditional.
Node first = expr.getFirstChild();
Node second = first.getNext();
Node last = expr.getLastChild();
// Isolate the children nodes.
expr.detachChildren();
// Transform the conditional to an IF statement.
Node cond = null;
Node trueExpr = new Node(Token.BLOCK);
Node falseExpr = new Node(Token.BLOCK);
switch (expr.getType()) {
case Token.HOOK:
// a = x?y:z --> if (x) {a=y} else {a=z}
cond = first;
trueExpr.addChildToFront(new Node(Token.EXPR_RESULT,
buildResultExpression(second, needResult, tempName)));
falseExpr.addChildToFront(new Node(Token.EXPR_RESULT,
buildResultExpression(last, needResult, tempName)));
break;
case Token.AND:
// a = x&&y --> if (a=x) {a=y} else {}
cond = buildResultExpression(first, needResult, tempName);
trueExpr.addChildToFront(new Node(Token.EXPR_RESULT,
buildResultExpression(last, needResult, tempName)));
break;
case Token.OR:
// a = x||y --> if (a=x) {} else {a=y}
cond = buildResultExpression(first, needResult, tempName);
falseExpr.addChildToFront(new Node(Token.EXPR_RESULT,
buildResultExpression(last, needResult, tempName)));
break;
default:
// With a valid tree we should never get here.
throw new IllegalStateException("Unexpected.");
}
Node ifNode;
if (falseExpr.hasChildren()) {
ifNode = new Node(Token.IF, cond, trueExpr, falseExpr);
} else {
ifNode = new Node(Token.IF, cond, trueExpr);
}
if (needResult) {
Node tempVarNode = new Node(Token.VAR,
Node.newString(Token.NAME, tempName));
Node injectionPointParent = injectionPoint.getParent();
injectionPointParent.addChildBefore(tempVarNode, injectionPoint);
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
injectionPointParent.addChildAfter(ifNode, tempVarNode);
// Replace the expression with the temporary name.
Node replacementValueNode = Node.newString(Token.NAME, tempName);
parent.replaceChild(expr, replacementValueNode);
} else {
// Only conditionals that are the direct child of an expression statement
// don't need results, for those simply replace the expression statement.
Preconditions.checkArgument(parent.getType() == Token.EXPR_RESULT);
Node gramps = parent.getParent();
gramps.replaceChild(parent, ifNode);
}
return ifNode;
}
/**
* Create an expression tree for an expression.
* If the result of the expression is needed, then:
* ASSIGN
* tempName
* expr
* otherwise, simply:
* expr
*/
private static Node buildResultExpression(
Node expr, boolean needResult, String tempName) {
if (needResult) {
return new Node(Token.ASSIGN,
Node.newString(Token.NAME, tempName),
expr);
} else {
return expr;
}
}
/**
* @param expr The expression to extract.
* @param injectionPoint The node before which to added the extracted
* expression.
* @return The extract statement node.
*/
private Node extractExpression(Node expr, Node injectionPoint) {
Node parent = expr.getParent();
// The temp value is known to be constant.
String tempName = getTempConstantValueName();
// Replace the expression with the temporary name.
Node replacementValueNode = Node.newString(Token.NAME, tempName);
parent.replaceChild(expr, replacementValueNode);
// Re-add the expression in the declaration of the temporary name.
Node tempNameNode = Node.newString(Token.NAME, tempName);
tempNameNode.addChildrenToBack(expr);
Node tempVarNode = new Node(Token.VAR, tempNameNode);
Node injectionPointParent = injectionPoint.getParent();
injectionPointParent.addChildBefore(tempVarNode, injectionPoint);
// If it is ASSIGN_XXX we need to assign it back to the original value.
// Note that calling the temp constant is a lie in this case, but we do know
// that
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> it is not modified until after the exposed expression.
if (NodeUtil.isAssignmentOp(parent) && !NodeUtil.isAssign(parent)) {
Node gParent = parent.getParent();
Node assignBack = new Node(Token.ASSIGN,
expr.cloneTree(),
tempNameNode.cloneNode());
if (NodeUtil.isExpressionNode(gParent)) {
gParent.getParent().addChildAfter(
NodeUtil.newExpr(assignBack), gParent);
} else {
// TODO(user): Use comma here sucks. We might close some accuracy
// in flow sensitive passes but as far as I know it is unavoidable.
Node comma = new Node(Token.COMMA);
gParent.replaceChild(parent, comma);
comma.addChildrenToFront(assignBack);
comma.addChildrenToFront(parent);
}
}
return tempVarNode;
}
/**
* Rewrite the call so "this" is preserved.
* a.b(c);
* becomes:
* var temp1 = a;
* var temp0 = temp1.b;
* temp0.call(temp1,c);
*
* @return The replacement node.
*/
private Node rewriteCallExpression(Node call, DecompositionState state) {
Preconditions.checkArgument(call.getType() == Token.CALL);
Node first = call.getFirstChild();
Preconditions.checkArgument(NodeUtil.isGet(first));
// Extracts the expression representing the function to call. For example:
// "a['b'].c" from "a['b'].c()"
Node getVarNode = extractExpression(
first, state.extractBeforeStatement);
state.extractBeforeStatement = getVarNode;
// Extracts the object reference to be used as "this". For example:
// "a['b']" from "a['b'].c"
Node getExprNode = getVarNode.getFirstChild().getFirstChild();
Preconditions.checkArgument(NodeUtil.isGet(getExprNode));
Node thisVarNode = extractExpression(
getExprNode.getFirstChild(), state.extractBeforeStatement);
state.extractBeforeStatement = thisVarNode;
// Rewrite the CALL expression.
Node thisNameNode = thisVarNode.getFirstChild();
Node
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> functionNameNode = getVarNode.getFirstChild();
// CALL
// GETPROP
// functionName
// "call"
// thisName
// original-parameter1
// original-parameter2
// ...
Node newCall = new Node(Token.CALL,
new Node(Token.GETPROP,
functionNameNode.cloneNode(),
Node.newString("call")),
thisNameNode.cloneNode(), call.getLineno(), call.getCharno());
// Throw away the call name
call.removeFirstChild();
if (call.hasChildren()) {
// Add the call parameters to the new call.
newCall.addChildrenToBack(call.removeChildren());
}
// Replace the call.
Node callParent = call.getParent();
callParent.replaceChild(call, newCall);
return newCall;
}
private String tempNamePrefix = "JSCompiler_temp_";
/**
* Allow the temp name to be overriden to make tests more readable.
*/
@VisibleForTesting
public void setTempNamePrefix(String tempNamePrefix) {
this.tempNamePrefix = tempNamePrefix;
}
/**
* Create a unique temp name.
*/
private String getTempValueName(){
return tempNamePrefix + safeNameIdSupplier.get();
}
/**
* Create a constant unique temp name.
*/
private String getTempConstantValueName(){
String sName = tempNamePrefix + "const_" + safeNameIdSupplier.get();
this.knownConstants.add(sName);
return sName;
}
/**
* @return For the subExpression, find the nearest statement Node before which
* it can be inlined. Null if no such location can be found.
*/
static Node findInjectionPoint(Node subExpression) {
Node expressionRoot = findExpressionRoot(subExpression);
Preconditions.checkNotNull(expressionRoot);
Node injectionPoint = expressionRoot;
Node parent = injectionPoint.getParent();
while (parent.getType() == Token.LABEL) {
injectionPoint = parent;
parent = injectionPoint.getParent();
}
Preconditions.checkState(
NodeUtil.isStatementBlock(injectionPoint.getParent()));
return injectionPoint;
}
/**
* @return Whether the node is a conditional
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> op.
*/
private static boolean isConditionalOp(Node n) {
switch(n.getType()) {
case Token.HOOK:
case Token.AND:
case Token.OR:
return true;
default:
return false;
}
}
/**
* @return The statement containing the expression. null if subExpression
* is not contain by in by a Node where inlining is known to be possible.
* For example, a WHILE node condition expression.
*/
static Node findExpressionRoot(Node subExpression) {
Node child = subExpression;
for (Node parent : child.getAncestors()) {
int parentType = parent.getType();
switch (parentType) {
// Supported expression roots:
// SWITCH and IF can have multiple children, but the CASE, DEFAULT,
// or BLOCK will be encountered first for any of the children other
// than the condition.
case Token.EXPR_RESULT:
case Token.IF:
case Token.SWITCH:
case Token.RETURN:
case Token.VAR:
Preconditions.checkState(child == parent.getFirstChild());
return parent;
// Any of these indicate an unsupported expression:
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
case Token.CASE:
case Token.DEFAULT:
return null;
}
child = parent;
}
throw new IllegalStateException("Unexpected AST structure.");
}
/**
* Determine whether a expression is movable, or can be be made movable be
* decomposing the containing expression.
*
* An subExpression is MOVABLE if it can be replaced with a temporary holding
* its results and moved to immediately before the root of the expression.
* There are three conditions that must be met for this to occur:
* 1) There must be a location to inject a statement for the expression. For
* example, this condition can not be met if the expression is a loop
* condition or CASE condition.
* 2) If the expression can be affect by side-effects, there can not be a
* side-effect between original location and the expression root.
* 3) If the expression has side-effects, there can not be any other
* expression that can be effected between the original location and the
* expression root.
*
*
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
this.ast = ast;
this.name = inputName;
this.isExtern = isExtern;
}
public CompilerInput(JSSourceFile file) {
this(file, false);
}
public CompilerInput(JSSourceFile file, boolean isExtern) {
this.ast = new JsAst(file);
this.name = file.getName();
this.isExtern = isExtern;
}
/** Returns a name for this input. Must be unique across all inputs. */
public String getName() {
return name;
}
@Override
public Node getAstRoot(AbstractCompiler compiler) {
return ast.getAstRoot(compiler);
}
@Override
public void clearAst() {
ast.clearAst();
}
@Override
public SourceFile getSourceFile() {
return ast.getSourceFile();
}
@Override
public void setSourceFile(SourceFile file) {
ast.setSourceFile(file);
}
/** Returns the SourceAst object on which this input is based. */
public SourceAst getSourceAst() {
return ast;
}
/** Gets a list of types depended on by this input. */
public Set<String> getRequires(AbstractCompiler compiler) {
if (getAstRoot(compiler) != null) {
DepsFinder deps = new DepsFinder(compiler, true);
NodeTraversal.traverse(compiler, getAstRoot(compiler), deps);
requires.addAll(deps.types);
return requires;
} else {
return ImmutableSet.<String>of();
}
}
/** Gets a list of types provided by this input. */
public Set<String> getProvides(AbstractCompiler compiler) {
if (getAstRoot(compiler) != null) {
DepsFinder deps = new DepsFinder(compiler, false);
NodeTraversal.traverse(compiler, getAstRoot(compiler), deps);
provides.addAll(deps.types);
return provides;
} else {
return ImmutableSet.<String>of();
}
}
private class DepsFinder extends AbstractShallowCallback {
private boolean findRequire;
private List<String> types;
private CodingConvention codingConvention;
DepsFinder(AbstractCompiler compiler, boolean findRequire) {
this.findRequire = findRequire;
this.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>codingConvention = compiler.getCodingConvention();
this.types = Lists.newArrayList();
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
String className = findRequire
? codingConvention.extractClassNameIfRequire(n, parent)
: codingConvention.extractClassNameIfProvide(n, parent);
if (className != null) {
types.add(className);
}
break;
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
return getSourceFile().getRegion(lineNumber);
}
public String getCode() throws IOException {
return getSourceFile().getCode();
}
/** Returns the module to which the input belongs. */
public JSModule getModule() {
return module;
}
/** Sets the module to which the input belongs. */
public void setModule(JSModule module) {
// An input may only belong to one module.
Preconditions.checkArgument(
module == null || this.module == null || this.module == module);
this.module = module;
}
public boolean isExtern() {
return isExtern;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> abstract T processSwitchCase(SwitchCase caseNode);
abstract T processSwitchStatement(SwitchStatement statementNode);
abstract T processThrowStatement(ThrowStatement statementNode);
abstract T processTryStatement(TryStatement statementNode);
abstract T processUnaryExpression(UnaryExpression exprNode);
abstract T processVariableDeclaration(VariableDeclaration declarationNode);
abstract T processVariableInitializer(VariableInitializer initializerNode);
abstract T processWhileLoop(WhileLoop loopNode);
abstract T processWithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>) {
return processBlock((Block) node);
} else if (node instanceof Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement) node);
case Token.CALL:
return processFunctionCall((FunctionCall) node);
case Token.CASE:
case Token.DEFAULT:
return processSwitchCase((SwitchCase) node);
case Token.CATCH:
case Token.FINALLY:
return processCatchClause((CatchClause) node);
case Token.COLON:
return processObjectProperty((ObjectProperty) node);
case Token.CONTINUE:
return processContinueStatement((ContinueStatement) node);
case Token.DO:
return processDoLoop((DoLoop) node);
case Token.EMPTY:
return processEmptyExpression((EmptyExpression) node);
case Token.EXPR_RESULT:
case Token.EXPR_VOID:
if (node instanceof ExpressionStatement) {
return processExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
return processLabeledStatement((LabeledStatement) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.DEBUGGER:
case Token.FALSE:
case Token.NULL:
case Token.THIS:
case Token.TRUE:
return processKeywordLiteral((KeywordLiteral) node);
case Token.FOR:
if (node instanceof ForInLoop) {
return processForInLoop((ForInLoop) node);
} else if (node instanceof ForLoop) {
return processForLoop((ForLoop) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.FUNCTION:
return processFunctionNode((FunctionNode) node);
case Token.GETELEM:
return processElementGet((ElementGet) node);
case Token.GETPROP:
return processPropertyGet
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return processRegExpLiteral((RegExpLiteral) node);
case Token.RETURN:
return processReturnStatement((ReturnStatement) node);
case Token.SCRIPT:
return processAstRoot((AstRoot) node);
case Token.STRING:
return processStringLiteral((StringLiteral) node);
case Token.SWITCH:
return processSwitchStatement((SwitchStatement) node);
case Token.THROW:
return processThrowStatement((ThrowStatement) node);
case Token.TRY:
return processTryStatement((TryStatement) node);
case Token.VAR:
if (node instanceof VariableDeclaration) {
return processVariableDeclaration((VariableDeclaration) node);
} else if (node instanceof VariableInitializer) {
return processVariableInitializer((VariableInitializer) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.WHILE:
return processWhileLoop((WhileLoop) node);
case Token.WITH:
return processWithStatement((WithStatement) node);
}
return processIllegalToken(node);
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> ");
break;
case 4:
sb.append(" ");
break;
}
}
/**
* Adds a new tracing statistic to a trace
*
* @param tracingStatistic to enable a run
* @return The index of this statistic (for use with stat.extraInfo()), or
* -1 if the statistic is not enabled.
*/
static int addTracingStatistic(TracingStatistic tracingStatistic) {
// Check to see if we can enable the tracing statistic before actually
// adding it.
if (tracingStatistic.enable()) {
// No synchronization needed, since this is a copy-on-write array.
extraTracingStatistics.add(tracingStatistic);
// 99.9% of the time, this will be O(1) and return extraTracingStatistics.length - 1
return extraTracingStatistics.lastIndexOf(tracingStatistic);
} else {
return -1;
}
}
/**
* For testing purposes only. These removes all current tracers. Severe errors can occur
* if there are any active tracers going on when this is called.
*
* The test suite uses this to remove any tracers that it has added.
*/
@VisibleForTesting
static void clearTracingStatisticsTestingOnly() {
extraTracingStatistics.clear();
}
/**
* Stop the trace.
* This may only be done once and must be done from the same thread
* that started it.
* @param silence_threshold Traces for time less than silence_threshold
* ms will be left out of the trace report. A value of -1 indicates
* that the current ThreadTrace silence_threshold should be used.
* @return The time that this trace actually ran
*/
long stop(int silence_threshold) {
Preconditions.checkState(Thread.currentThread() == startThread);
ThreadTrace trace = getThreadTrace();
// Do nothing if the thread trace was not initialized.
if (!trace.isInitialized()) {
return 0;
}
stopTimeMs = clock.currentTimeMillis();
if (extraTracingValues != null) {
// We use extraTracingValues.length rather than extraTracingStatistics.size() because
// a new statistic may have been added
for (int i = 0; i < extraTracingValues.length; i++) {
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Statistics.get(i).getUnits());
sb.append("; ");
}
}
}
sb.append(indent);
sb.append(tracer.toString());
return sb.toString();
}
}
/** Stores a thread's Trace */
static final class ThreadTrace {
/** Events taking less than this number of milliseconds are not reported. */
int defaultSilenceThreshold; // non-final
/** The Events corresponding to each startEvent/stopEvent */
final ArrayList<Event> events = new ArrayList<Event>();
/** Tracers that have not had their .stop() called */
final HashSet<Tracer> outstandingEvents = new HashSet<Tracer>();
/** Map from type to Stat object */
final Map<String, Stat> stats = new HashMap<String, Stat>();
/**
* True if {@code outstandingEvents} has been cleared because we exceeded
* the max trace limit.
*/
boolean isOutstandingEventsTruncated = false;
/**
* True if {@code events} has been cleared because we exceeded the max
* trace limit.
*/
boolean isEventsTruncated = false;
/**
* Set to true if {@link Tracer#initCurrentThreadTrace()} was called by
* the current thread.
*/
boolean isInitialized = false;
/**
* Whether pretty printing is enabled for the trace.
*/
boolean prettyPrint = false;
/** Initialize the trace. */
void init() {
isInitialized = true;
}
/** Is initialized? */
boolean isInitialized() {
return isInitialized;
}
/**
* Called by the constructor {@link Tracer#Tracer(String, String)} to create
* a start event.
*/
void startEvent(Tracer t) {
events.add(new Event(true, t));
boolean notAlreadyOutstanding = outstandingEvents.add(t);
Preconditions.checkState(notAlreadyOutstanding);
}
/**
* Called by {@link Tracer#stop()} to create a stop event.
*/
void endEvent(Tracer t, int silenceThreshold) {
boolean wasOutstanding = outstandingEvents.remove(t);
if (!wasOutstanding) {
if (isOutstandingEventsTruncated) {
// The events stack overflowed and was truncated, so just log a
// warning. Otherwise, we get an exception which is extremely
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> // confusing.
logger.log(Level.WARNING,
"event not found, probably because the event stack "
+ "overflowed and was truncated",
new Throwable());
} else {
// throw an exception if the event was not found and the events stack
// is pristine
throw new IllegalStateException();
}
}
long elapsed = t.stopTimeMs - t.startTimeMs;
if (silenceThreshold == -1) { // use default
silenceThreshold = defaultSilenceThreshold;
}
if (elapsed < silenceThreshold) {
// If this one is silent then we need to remove the start Event
boolean removed = false;
for (int i = 0; i < events.size(); i++) {
Event e = events.get(i);
if (e.tracer == t) {
Preconditions.checkState(e.isStart);
events.remove(i);
removed = true;
break;
}
}
// Only assert if we didn't find the original and the events
// weren't truncated.
Preconditions.checkState(removed || isEventsTruncated);
} else {
events.add(new Event(false, t));
}
if (t.type != null) {
Stat stat = stats.get(t.type);
if (stat == null) {
stat = new Stat();
if (!extraTracingStatistics.isEmpty()) {
stat.extraInfo = new int[extraTracingStatistics.size()];
}
stats.put(t.type, stat);
}
stat.count++;
if (typeToCountMap != null) {
typeToCountMap.incrementBy(t.type, 1);
}
stat.clockTime += elapsed;
if (typeToTimeMap != null) {
typeToTimeMap.incrementBy(t.type, elapsed);
}
if (stat.extraInfo != null && t.extraTracingValues != null) {
int overlapLength = Math.min(stat.extraInfo.length, t.extraTracingValues.length);
for (int i = 0; i < overlapLength; i++) {
stat.extraInfo[i] += t.extraTracingValues[i];
AtomicTracerStatMap map = extraTracingStatistics.get(i).getTracingStat();
if (map !=
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
!(parent.getType() == Token.VAR &&
!parent.hasOneChild())) {
docInfo = parent.getJSDocInfo();
}
// Try to find the type of the NAME.
JSType varType = n.getJSType();
if (varType == null && parent.getType() == Token.FUNCTION) {
varType = parent.getJSType();
}
// If we have no type to attach JSDocInfo to, then there's nothing
// we can do.
if (varType == null || docInfo == null) {
return;
}
// Dereference the type. If the result is not an object, or already
// has docs attached, then do nothing.
ObjectType objType = dereferenceToObject(varType);
if (objType == null || objType.getJSDocInfo() != null) {
return;
}
attachJSDocInfoToNominalTypeOrShape(objType, docInfo, n.getString());
break;
case Token.GETPROP:
// Infer JSDocInfo on properties.
// There are two ways to write doc comments on a property.
//
// 1)
// /** @deprecated */
// obj.prop = ...
//
// 2)
// /** @deprecated */
// obj.prop;
if (NodeUtil.isExpressionNode(parent) ||
(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n)) {
docInfo = n.getJSDocInfo();
if (docInfo == null) {
docInfo = parent.getJSDocInfo();
}
if (docInfo != null) {
ObjectType lhsType =
dereferenceToObject(n.getFirstChild().getJSType());
if (lhsType != null) {
// Put the JSDoc in the property slot, if there is one.
String propName = n.getLastChild().getString();
if (lhsType.hasOwnProperty(propName)) {
lhsType.setPropertyJSDocInfo(propName, docInfo, inExterns);
}
// Put the JSDoc in any constructors or function shapes as well.
ObjectType propType =
dereferenceToObject(lhsType.getPropertyType(propName));
if (propType != null) {
attachJ
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> path through
// the code identical to how it's been for years.
this.outputCharsetEncoder = null;
} else {
this.outputCharsetEncoder = outputCharset.newEncoder();
}
}
CodeGenerator(CodeConsumer consumer, Charset outputCharset) {
this(consumer, outputCharset, true);
}
CodeGenerator(CodeConsumer consumer) {
this(consumer, null, false);
}
void add(String str) {
cc.add(str);
}
private void addIdentifier(String identifier) {
cc.addIdentifier(identifierEscape(identifier));
}
void add(Node n) {
add(n, Context.OTHER);
}
void add(Node n, Context context) {
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null && first != last) {
Preconditions.checkState(childCount == 2);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p, context);
cc.addOp(opstr, true);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
// Handle associativity.
// e.g. if the parse tree is a * (b * c),
// we can simply generate a * b * c.
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
// Assignments are the only right-associative binary operators
addExpr(last, p, rhsContext);
} else {
addExpr(last, p + 1, rhsContext);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
Preconditions.checkState(first.getNext().getType() ==
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> Token.BLOCK &&
first.getNext().getChildCount() <= 1);
Preconditions.checkState(childCount >= 2 && childCount <= 3);
add("try");
add(first, Context.PRESERVE_BLOCK);
// second child contains the catch block, or nothing if there
// isn't a catch block
Node catchblock = first.getNext().getFirstChild();
if (catchblock != null) {
add(catchblock);
}
if (childCount == 3) {
add("finally");
add(last, Context.PRESERVE_BLOCK);
}
break;
}
case Token.CATCH:
Preconditions.checkState(childCount == 3);
if (first.getNext().getType() != Token.EMPTY) {
throw new Error("Catch conditions not suppored because I think" +
" that it may be a netscape only feature.");
}
add("catch(");
add(first);
add(")");
add(last, Context.PRESERVE_BLOCK);
break;
case Token.THROW:
Preconditions.checkState(childCount == 1);
add("throw");
add(first);
// Must have a ';' after a throw statement, otherwise safari can't
// parse this.
cc.endStatement(true);
break;
case Token.RETURN:
add("return");
if (childCount == 1) {
add(first);
} else {
Preconditions.checkState(childCount == 0);
}
cc.endStatement();
break;
case Token.VAR:
if (first != null) {
add("var ");
addList(first, false, getContextForNoInOperator(context));
}
break;
case Token.NAME:
if (first == null || first.getType() == Token.EMPTY) {
addIdentifier(n.getString());
} else {
Preconditions.checkState(childCount == 1);
addIdentifier(n.getString());
cc.addOp("=", true);
if (first.getType() == Token.COMMA) {
addExpr(first, NodeUtil.precedence(Token.ASSIGN));
} else {
// Add expression, consider nearby code at lowest level of
// precedence.
addExpr(first, 0, getContextForNoInOperator
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>(context));
}
}
break;
case Token.ARRAYLIT:
add("[");
addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP));
add("]");
break;
case Token.LP:
add("(");
addList(first);
add(")");
break;
case Token.COMMA:
addList(first, false, context);
break;
case Token.NUMBER:
Preconditions.checkState(childCount == 0);
cc.addNumber(n.getDouble());
break;
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: {
// All of these unary operators are right-associative
Preconditions.checkState(childCount == 1);
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type));
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), p);
cc.addOp(":", true);
addExpr(last, p);
break;
}
case Token.REGEXP:
if (first.getType() != Token.STRING ||
last.getType() != Token.STRING) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.GET_REF:
add(first);
break;
case Token.REF_SPECIAL:
Preconditions.checkState(childCount == 1);
add(first);
add(".");
add((String) n.getProp(Node.NAME_PROP));
break;
case Token.FUNCTION:
Preconditions.checkState(childCount == 3);
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> boolean funcNeedsParens = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.SCRIPT:
case Token.BLOCK: {
boolean stripBlock = n.isSyntheticBlock() ||
((context != Context.PRESERVE_BLOCK) && (n.getChildCount() < 2));
if (!stripBlock) {
cc.beginBlock();
}
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.getType() == Token.VAR) {
cc.endStatement();
}
if (c.getType() == Token.FUNCTION) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top level statements are more homogeneous.
if (type == Token.SCRIPT) {
cc.notePreferredLineBreak();
}
}
if (!stripBlock) {
cc.endBlock(context == Context.STATEMENT);
}
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.getType() == Token.VAR) {
add(first, Context.IN_FOR_INIT_CLAUSE);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
}
break
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyExpression(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP: {
Preconditions.checkState(childCount == 2);
Preconditions.checkState(last.getType() == Token.STRING);
boolean needsParens = (first.getType() == Token.NUMBER);
if (needsParens) {
add("(");
}
addLeftExpr(first, NodeUtil.precedence(type), context);
if (needsParens) {
add(")");
}
add(".");
addIdentifier(last.getString());
break;
}
case Token.GETELEM:
Preconditions.checkState(childCount == 2);
addLeftExpr(first, NodeUtil.precedence(type), context);
add("[");
add(first.getNext());
add("]");
break;
case Token.WITH:
Preconditions.checkState(childCount == 2);
add("with(");
add(first);
add(")");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.INC:
case Token.DEC: {
Preconditions.checkState(childCount == 1);
String o = type == Token.INC ? "++" : "--";
int postProp = n.getIntProp(Node.INCRDECR_PROP, 0);
// A non-zero post-prop value indicates a post inc/dec, default of zero
// is a pre-inc/dec.
if (postProp != 0) {
addLeftExpr(first, NodeUtil.precedence(type), context);
cc.addOp(o, false);
} else {
cc.addOp(o, false);
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
add(first);
}
break;
}
case Token.CALL:
// If the left hand side of the call is a direct reference to eval,
// then it must have a DIRECT_EVAL annotation. If it does not, then
// that means it was originally an indirect call to eval, and that
// indirectness must be preserved.
if (first.getType() == Token.NAME &&
"eval".equals(first.getString()) &&
!first.getBooleanProp(Node.DIRECT_EVAL)) {
add("(0,eval)");
} else {
addLeftExpr(first, NodeUtil.precedence(type), context);
}
add("(");
addList(first.getNext());
add(")");
break;
case Token.IF:
boolean hasElse = childCount == 3;
boolean ambiguousElseClause =
context == Context.BEFORE_DANGLING_ELSE && !hasElse;
if (ambiguousElseClause) {
cc.beginBlock();
}
add("if(");
add(first);
add(")");
if (hasElse) {
addNonEmptyExpression(
first.getNext(), Context.BEFORE_DANGLING_ELSE, false);
add("else");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), false);
} else {
addNonEmptyExpression(first.getNext(), Context.OTHER, false);
Preconditions.checkState(childCount == 2);
}
if (ambiguousElseClause) {
cc.endBlock();
}
break;
case Token.NULL:
case Token.THIS:
case Token.FALSE:
case Token.TRUE:
Preconditions.checkState(childCount == 0);
add(Node.tokenToName(type));
break;
case Token.CONTINUE:
Preconditions.checkState(childCount <= 1);
add("continue");
if (childCount == 1) {
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.DEBUGGER:
Preconditions.checkState(childCount == 0);
add("debugger");
cc.endStatement();
break;
case Token.BREAK:
Preconditions.checkState(childCount <= 1);
add("break");
if (childCount
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> == 1) {
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.EXPR_VOID:
case Token.EXPR_RESULT:
if (type == Token.EXPR_VOID && validation) {
throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT.");
}
Preconditions.checkState(childCount == 1);
add(first, Context.START_OF_EXPR);
cc.endStatement();
break;
case Token.NEW:
add("new ");
int precedence = NodeUtil.precedence(type);
// If the first child contains a CALL, then claim higher precedence
// to force parens. Otherwise, when parsed, NEW will bind to the
// first viable parens
if (NodeUtil.containsCall(first)) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING:
Preconditions.checkState(childCount == 0);
add(jsString(n.getString(), outputCharsetEncoder));
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
Preconditions.checkState(childCount % 2 == 0);
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens) {
add("(");
}
add("{");
for (Node c = first; c != null; c = c.getNext().getNext()) {
if (c != first) {
cc.listSeparator();
}
// Object literal property names don't have to be quoted if they are
// not JavaScript keywords
if (c.getType() == Token.STRING &&
!TokenStream.isKeyword(c.getString()) &&
TokenStream.isJSIdentifier(c.getString()) &&
// do not encode literally any non-literal characters that were
// unicode escaped.
NodeUtil.isLatin(c.getString()))
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> {
add(c.getString());
} else {
addExpr(c, 1);
}
add(":");
addExpr(c.getNext(), 1);
}
add("}");
if (needsParens) {
add(")");
}
break;
}
case Token.SWITCH:
add("switch(");
add(first);
add(")");
cc.beginBlock();
addAllSiblings(first.getNext());
cc.endBlock(context == Context.STATEMENT);
break;
case Token.CASE:
Preconditions.checkState(childCount == 2);
add("case ");
add(first);
addCaseBody(last);
break;
case Token.DEFAULT:
Preconditions.checkState(childCount == 1);
add("default");
addCaseBody(first);
break;
case Token.LABEL:
Preconditions.checkState(childCount == 2);
add(first);
add(":");
addNonEmptyExpression(
last, getContextForNonEmptyExpression(context), true);
break;
// This node is auto generated in anonymous functions and should just get
// ignored for our purposes.
case Token.SETNAME:
break;
default:
throw new Error("Unknown type " + type + "\n" + n.toStringTree());
}
cc.endSourceMapping(n);
}
/**
* Adds a block or expression, substituting a VOID with an empty statement.
* This is used for "for (...);" and "if (...);" type statements.
*
* @param n The node to print.
* @param context The context to determine how the node should be printed.
*/
private void addNonEmptyExpression(
Node n, Context context, boolean allowNonBlockChild) {
Node nodeToProcess = n;
if (!allowNonBlockChild && n.getType() != Token.BLOCK) {
if (validation) {
throw new Error("Missing BLOCK child.");
}
}
// Strip unneeded blocks, that is blocks with <2 children unless
// the CodePrinter specifically wants to keep them.
if (n.getType() == Token.BLOCK ) {
int count = getNonEmptyChildCount(n);
if (count == 0) {
cc.endStatement(true);
return;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
}
if (count == 1) {
// Hack around a couple of browser bugs:
// Safari needs a block around function declarations.
// IE6/7 needs a block around DOs.
Node firstAndOnlyChild = getFirstNonEmptyChild(n);
boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks();
if (alwaysWrapInBlock ||
firstAndOnlyChild.getType() == Token.FUNCTION ||
firstAndOnlyChild.getType() == Token.DO) {
cc.beginBlock();
add(firstAndOnlyChild, Context.STATEMENT);
cc.maybeLineBreak();
cc.endBlock(context == Context.STATEMENT);
return;
} else {
// Continue with the only child.
nodeToProcess = firstAndOnlyChild;
}
}
}
if (nodeToProcess.getType() == Token.EMPTY) {
cc.endStatement(true);
} else {
add(nodeToProcess, context);
// VAR doesn't include ';' since it gets used in expressions - so any
// VAR in a statement context needs a call to endStatement() here.
if (nodeToProcess.getType() == Token.VAR) {
cc.endStatement();
}
}
}
/**
* Adds a node at the left-hand side of an expression. Unlike
* {@link #addExpr(Node,int)}, this preserves information about the context.
*
* The left side of an expression is special because in the JavaScript
* grammar, certain tokens may be parsed differently when they are at
* the beginning of a statement. For example, "{}" is parsed as a block,
* but "{'x': 'y'}" is parsed as an object literal.
*/
void addLeftExpr(Node n, int minPrecedence, Context context) {
addExpr(n, minPrecedence, context);
}
void addExpr(Node n, int minPrecedence) {
addExpr(n, minPrecedence, Context.OTHER);
}
private void addExpr(Node n, int minPrecedence, Context context) {
if ((NodeUtil.precedence(n.getType()) < minPrecedence) ||
((context == Context.IN_FOR_INIT_CLAUSE) &&
(n.getType() == Token.IN))
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> s = scope.getParent();
if (s == null) {
throw new IllegalArgumentException("Var is not local");
}
while (s.getParent() != null) {
num += s.getVarCount();
s = s.getParent();
}
return num;
}
/**
* Returns whether this is a global variable.
*/
public boolean isGlobal() {
return scope.isGlobal();
}
/**
* Returns whether this is a local variable.
*/
public boolean isLocal() {
return scope.isLocal();
}
/**
* Returns whether this is defined in an extern file.
*/
boolean isExtern() {
return input == null || input.isExtern();
}
/**
* Returns {@code true} if the variable is declared as a constant,
* based on the value reported by {@code NodeUtil}.
*/
public boolean isConst() {
return NodeUtil.isConstantName(nameNode);
}
/**
* Returns {@code true} if the variable is declared as a define.
* A variable is a define if it is annotaed by {@code @define}.
*/
public boolean isDefine() {
return isDefine;
}
public Node getInitialValue() {
Node parent = getParentNode();
return parent.getType() == Token.FUNCTION ?
parent : nameNode.getFirstChild();
}
/**
* Gets this variable's type. To know whether this type has been inferred,
* see {@code #isInferred()}.
*/
public JSType getType() {
return type;
}
/**
* Returns the name node that produced this variable.
*/
public Node getNameNode() {
return nameNode;
}
/**
* Gets the JSDocInfo for the variable.
*/
public JSDocInfo getJSDocInfo() {
return info;
}
/**
* Sets this variable's type.
* @throws IllegalStateException if the variable's type is not inferred
*/
void setType(JSType type) {
Preconditions.checkState(isTypeInferred());
this.type = type;
}
/**
* Returns whether this variable's type is inferred. To get the variable's
* type, see {@link #getType()}.
*/
public boolean isTypeInferred() {
return typeInferred;
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
public String getInputName() {
if (input == null)
return "<non-file>";
else
return input.getName();
}
public boolean isNoShadow() {
if (info != null && info.isNoShadow()) {
return true;
} else {
return false;
}
}
@Override public boolean equals(Object other) {
if (!(other instanceof Var)) {
return false;
}
Var otherVar = (Var) other;
return otherVar.nameNode == nameNode;
}
@Override public int hashCode() {
return nameNode.hashCode();
}
@Override
public String toString() {
return "Scope.Var " + name;
}
}
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkArgument(rootNode != parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType instanceof FunctionType) {
thisType = ((FunctionType) nodeType).getTypeOfThis();
} else {
thisType = parent.thisType;
}
this.isBottom = false;
}
/**
* Creates a global Scope.
* @param rootNode Typically the global BLOCK node.
*/
Scope(Node rootNode, AbstractCompiler compiler) {
this.parent = null;
this.rootNode = rootNode;
thisType = compiler.getTypeRegistry().getNativeObjectType(GLOBAL_THIS);
this.isBottom = false;
}
/**
* Creates a empty Scope (bottom of the lattice).
* @param rootNode Typically a FUNCTION node or the global BLOCK node.
* @param thisType the type of {@code this} in this scope
*/
Scope(Node rootNode, ObjectType thisType) {
this.parent = null;
this.rootNode = rootNode;
this.thisType = thisType;
this.isBottom = true;
}
/** Whether this is the bottom of the lattice. */
boolean isBottom
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>() {
return isBottom;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope<JSType> getParentScope() {
return parent;
}
/**
* Gets the type of {@code this} in the current scope.
*/
public ObjectType getTypeOfThis() {
return thisType;
}
/**
* Declares a variable whose type is inferred.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, JSType type, CompilerInput input) {
return declare(name, nameNode, type, input, true);
}
/**
* Declares a variable.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
* @param inferred Whether this variable's type is inferred (as opposed
* to declared).
*/
Var declare(String name, Node nameNode,
JSType type, CompilerInput input, boolean inferred) {
Preconditions.checkState(name != null && name.length() > 0);
// Make sure that it's declared only once
Preconditions.checkState(vars.get(name) == null);
Var var = new Var(inferred);
var.name = name;
var.nameNode = nameNode;
var.type = type;
var.scope = this;
var.index = vars.size();
var.input = input;
// native variables do not have a name node.
// TODO(user): make Var abstract and have NativeVar, NormalVar.
JSDocInfo info = NodeUtil.getInfoForNameNode(nameNode);
var
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.isDefine = info != null && info.isDefine();
var.info = info;
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void undeclare(Var var) {
Preconditions.checkState(var.scope == this);
Preconditions.checkState(vars.get(var.name) == var);
vars.remove(var.name);
}
public StaticSlot<JSType> getSlot(String name) {
return getVar(name);
}
public StaticSlot<JSType> getOwnSlot(String name) {
return vars.get(name);
}
/**
* Returns the variable, may be null
*/
public Var getVar(String name) {
Var var = vars.get(name);
if (var != null) {
return var;
} else if (parent != null) { // Recurse up the parent Scope
return parent.getVar(name);
} else {
return null;
}
}
/**
* Returns true if a variable is declared.
*/
public boolean isDeclared(String name, boolean recurse) {
Scope scope = this;
if (scope.vars.containsKey(name))
return true;
if (scope.parent != null && recurse) {
return scope.parent.isDeclared(name, recurse);
}
return false;
}
/**
* Return an iterator over all of the variables declared in this scope.
*/
public Iterator<Var> getVars() {
return vars.values().iterator();
}
/**
* Returns number of variables in this scope
*/
public int getVarCount() {
return vars.size();
}
/**
* Returns whether this is the global scope.
*/
public boolean isGlobal() {
return parent == null;
}
/**
* Returns whether this is a local scope (i.e. not the global scope).
*/
public boolean isLocal() {
return !isGlobal();
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> edges
// on the next iteration.
DiGraphNode<N, E> source = workSet.iterator().next();
N sourceValue = source.getValue();
workSet.remove(source);
List<DiGraphEdge<N, E>> outEdges = source.getOutEdges();
for (DiGraphEdge<N, E> edge : outEdges) {
N destNode = edge.getDestination().getValue();
if (callback.traverseEdge(sourceValue, edge.getValue(), destNode)) {
workSet.add(edge.getDestination());
}
}
}
Preconditions.checkState(cycleCount != maxIterations,
NON_HALTING_ERROR_MSG);
}
public static interface EdgeCallback<Node, Edge> {
/**
* Update the state of the destination node when the given edge
* is traversed. For the fixed-point computation to work, only the
* destination node may be modified. The source node and the edge must
* not be modified.
*
* @param source The start node.
* @param e The edge.
* @param destination The end node.
* @return Whether the state of the destination node changed.
*/
boolean traverseEdge(Node source, Edge e, Node destination);
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>_PARAM = DiagnosticType.warning(
"JSC_INEXISTANT_PARAM",
"parameter {0} does not appear in {1}''s parameter list");
static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning(
"JSC_TYPE_REDEFINITION",
"attempted re-definition of type {0}\n"
+ "found : {1}\n"
+ "expected: {2}");
static final DiagnosticType TEMPLATE_TYPE_DUPLICATED = DiagnosticType.error(
"JSC_TEMPLATE_TYPE_DUPLICATED",
"Only one parameter type must be the template type");
static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.error(
"JSC_TEMPLATE_TYPE_EXPECTED",
"The template type must be a parameter type");
/**
* @param fnName The function name.
* @param compiler The compiler.
* @param errorRoot The node to associate with any warning generated by
* this builder.
* @param sourceName A source name for associating any warnings that
* we have to emit.
* @param scope The syntactic scope.
*/
FunctionTypeBuilder(String fnName, AbstractCompiler compiler,
Node errorRoot, String sourceName, Scope scope) {
Preconditions.checkNotNull(errorRoot);
this.fnName = fnName == null ? "" : fnName;
this.codingConvention = compiler.getCodingConvention();
this.typeRegistry = compiler.getTypeRegistry();
this.errorRoot = errorRoot;
this.sourceName = sourceName;
this.compiler = compiler;
this.scope = scope;
}
/**
* Sets the FUNCTION node of this function.
*/
FunctionTypeBuilder setSourceNode(@Nullable Node sourceNode) {
this.sourceNode = sourceNode;
return this;
}
/**
* Infer the parameter and return types of a function from
* the parameter and return types of the function it is overriding.
*
* @param oldType The function being overridden.
* @param paramsParent The LP node of the function that we're assigning to.
* If null, that just means we're not initializing this to a function
* literal.
*/
FunctionTypeBuilder inferFromOverriddenFunction(
FunctionType oldType, @Nullable Node paramsParent) {
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>, baseType.toString());
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName);
}
}
// implemented interfaces
if (isConstructor || isInterface) {
implementedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
ObjectType interType = ObjectType.cast(t.evaluate(scope));
if (interType != null) {
implementedInterfaces.add(interType);
} else {
reportError(BAD_IMPLEMENTED_TYPE, fnName);
}
}
if (baseType != null) {
JSType maybeFunctionType = baseType.getConstructor();
if (maybeFunctionType instanceof FunctionType) {
FunctionType functionType = baseType.getConstructor();
Iterables.addAll(
implementedInterfaces,
functionType.getImplementedInterfaces());
}
}
} else if (info.getImplementedInterfaceCount() > 0) {
reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName);
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param type The type of this.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) {
ObjectType objType = ObjectType.cast(type);
if (objType != null && (info == null || !info.hasType())) {
thisType = objType;
}
return this;
}
/**
* Infers the type of {@code this}.
* @param info The JSDocInfo for this function.
* @param owner The node for the object whose prototype "owns" this function.
* For example, {@code A} in the expression {@code A.prototype.foo}. May
* be null to indicate that this is not a prototype property.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info,
@Nullable Node owner) {
ObjectType maybeThisType = null;
if (info != null && info.hasThisType()) {
maybeThisType = ObjectType.cast(info.getThisType().evaluate(scope));
}
if (maybeThisType != null) {
// TODO(user): Doing an instanceof check here is too
// restrictive as (Date,Error) is, for instance, an object
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
functionType.defineDeclaredProperty("getInstance", getterType, false);
functionType.defineDeclaredProperty("instance_", objectType, false);
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.isObject");
@Override
public boolean isPropertyTestFunction(Node call) {
Preconditions.checkArgument(call.getType() == Token.CALL);
return propertyTestFunctions.contains(
call.getFirstChild().getQualifiedName());
}
@Override
public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t,
Node callNode) {
Preconditions.checkArgument(callNode.getType() == Token.CALL);
Node callName = callNode.getFirstChild();
if (!"goog.reflect.object".equals(callName.getQualifiedName()) ||
callName.getChildCount() != 2) {
return null;
}
Node typeNode = callName.getNext();
if (!typeNode.isQualifiedName()) {
return null;
}
Node objectNode = typeNode.getNext();
if (objectNode.getType() != Token.OBJECTLIT) {
t.getCompiler().report(JSError.make(t.getSourceName(), callNode,
OBJECTLIT_EXPECTED));
return null;
}
return new ObjectLiteralCast(typeNode.getQualifiedName(),
typeNode.getNext());
}
/**
* {@inheritDoc}
*/
@Override
public boolean isOptionalParameter(Node parameter) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isVarArgsParameter(Node parameter) {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPrivate(String name) {
return false;
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>() {
return getBooleanProp(QUOTED_PROP);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
@Override public void setQuotedString() {
putBooleanProp(QUOTED_PROP, true);
}
private String str;
}
private static class PropListItem implements Serializable
{
private static final long serialVersionUID = 1L;
PropListItem next;
int type;
int intValue;
Object objectValue;
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType;
parent = null;
first = last = child;
child.next = null;
child.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node right) {
Preconditions.checkArgument(left.parent == null,
"first new child has existing parent");
Preconditions.checkArgument(left.next == null,
"first new child has existing sibling");
Preconditions.checkArgument(right.parent == null,
"second new child has existing parent");
Preconditions.checkArgument(right.next == null,
"second new child has existing sibling");
type = nodeType;
parent = null;
first = left;
last = right;
left.next = right;
left.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> = this;
mid.next = right;
mid.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(mid2.parent == null);
Preconditions.checkArgument(mid2.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = mid2;
mid.parent = this;
mid2.next = right;
mid2.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, int lineno, int charno) {
type = nodeType;
parent = null;
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node child, int lineno, int charno) {
this(nodeType, child);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node right, int lineno, int charno) {
this(nodeType, left, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node right,
int lineno, int charno) {
this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(
children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first)
return null;
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null)
throw new RuntimeException("node is not a child");
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
}
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = first;
first = child;
if (last == null) {
last = child;
}
}
public void addChildToBack(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = null;
if (last == null) {
first = last = child;
return;
}
last.next = child;
last = child;
}
public void addChildrenToFront(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSib = children.getLastSibling();
lastSib.next = first;
first = children;
if (last == null) {
last = lastSib;
}
}
public void addChildrenToBack(Node children) {
for (Node child = children; child != null; child = child.next) {
// Hmmm... IRFactory doesn't remove before calling this.
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
if (last != null) {
last.next = children;
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> last = children.getLastSibling();
if (first == null) {
first = children;
}
}
/**
* Add 'child' before 'node'.
*/
public void addChildBefore(Node newChild, Node node) {
Preconditions.checkArgument(node != null,
"The existing child node of the parent should not be null.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
if (first == node) {
newChild.parent = this;
newChild.next = first;
first = newChild;
return;
}
Node prev = getChildBefore(node);
addChildAfter(newChild, prev);
}
/**
* Add 'child' after 'node'.
*/
public void addChildAfter(Node newChild, Node node) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
newChild.parent = this;
newChild.next = node.next;
node.next = newChild;
if (last == node) {
last = newChild;
}
}
/**
* Detach a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChildBefore(child);
if (prev == null)
first = first.next;
else
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child ==
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> first) {
first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
private PropListItem lookupProperty(int propType)
{
PropListItem x = propListHead;
while (x != null && propType != x.type) {
x = x.next;
}
return x;
}
private PropListItem ensureProperty(int propType)
{
PropListItem item = lookupProperty(propType);
if (item == null) {
item = new PropListItem();
item.type = propType;
item.next = propListHead;
propListHead = item;
}
return item;
}
public void removeProp(int propType)
{
PropListItem x = propListHead;
if (x != null) {
PropListItem prev = null;
while (x.type != propType) {
prev = x;
x = x.next;
if (x == null) { return; }
}
if (prev == null) {
propListHead = x.next;
} else {
prev.next = x.next;
}
}
}
public Object getProp(int propType)
{
PropListItem item = lookupProperty(propType);
if (item == null) { return null; }
return item.objectValue;
}
public
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Double(double s) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(
this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(
this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public void setString(String s) throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(
this + " is not a string node");
}
}
@Override public String toString()
{
return toString(true, true, true);
}
public String toString(
boolean printSource,
boolean printAnnotations,
boolean printType)
{
if (Token.printTrees) {
StringBuilder sb = new StringBuilder();
toString(sb, printSource, printAnnotations, printType);
return sb.toString();
}
return String.valueOf(type);
}
private void toString(
StringBuilder sb,
boolean printSource,
boolean printAnnotations,
boolean printType)
{
if (Token.printTrees) {
sb.append(Token.name(type));
if (this instanceof StringNode) {
sb.append(' ');
sb.append(getString());
} else if (type == Token.FUNCTION) {
sb.append(' ');
sb.append(first.getString());
} else if (this instanceof ScriptOrFnNode) {
ScriptOrFnNode sof = (ScriptOrFnNode)this;
if (this instanceof FunctionNode) {
FunctionNode fn = (FunctionNode)this;
sb.append(' ');
sb.append(fn.getFunctionName());
}
if (printSource) {
sb.append(" [source name: ");
sb.append(sof.getSourceName());
sb.append("] [encoded source length: ");
sb.append
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> Node.children is in for
// loops, this branch is extremely unlikely.
return (new SiblingNodeIterable(start)).iterator();
}
}
public boolean hasNext() {
return current != null;
}
public Node next() {
if (current == null) {
throw new NoSuchElementException();
}
try {
return current;
} finally {
current = current.getNext();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
//==========================================================================
// Accessors
public Node getParent() {
return parent;
}
/**
* Gets the ancestor node relative to this.
* @param level 0 = this, 1 = the parent, etc.
*/
public Node getAncestor(int level) {
Preconditions.checkArgument(level >= 0);
Node node = this;
while(node != null && level-- > 0) {
node = node.getParent();
}
return node;
}
/**
* Iterates all of the node's ancestors excluding itself.
*/
public AncestorIterable getAncestors() {
return new AncestorIterable(this.getParent());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
public Iterator<Node> iterator() {
return new Iterator<Node>() {
public boolean hasNext() {
return cur != null;
}
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
Node n = cur;
cur = cur.getParent();
return n;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
* @return Whether the node more than
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>NAME: return "typeofname";
case Token.THISFN: return "thisfn";
case Token.SEMI: return "semi";
case Token.LB: return "lb";
case Token.RB: return "rb";
case Token.LC: return "lc";
case Token.RC: return "rc";
case Token.LP: return "lp";
case Token.RP: return "rp";
case Token.COMMA: return "comma";
case Token.ASSIGN: return "assign";
case Token.ASSIGN_BITOR: return "assign_bitor";
case Token.ASSIGN_BITXOR: return "assign_bitxor";
case Token.ASSIGN_BITAND: return "assign_bitand";
case Token.ASSIGN_LSH: return "assign_lsh";
case Token.ASSIGN_RSH: return "assign_rsh";
case Token.ASSIGN_URSH: return "assign_ursh";
case Token.ASSIGN_ADD: return "assign_add";
case Token.ASSIGN_SUB: return "assign_sub";
case Token.ASSIGN_MUL: return "assign_mul";
case Token.ASSIGN_DIV: return "assign_div";
case Token.ASSIGN_MOD: return "assign_mod";
case Token.HOOK: return "hook";
case Token.COLON: return "colon";
case Token.OR: return "or";
case Token.AND: return "and";
case Token.INC: return "inc";
case Token.DEC: return "dec";
case Token.DOT: return "dot";
case Token.FUNCTION: return "function";
case Token.EXPORT: return "export";
case Token.IMPORT: return "import";
case Token.IF: return "if";
case Token.ELSE: return "else";
case Token.SWITCH: return "switch";
case Token.CASE: return "case";
case Token.DEFAULT: return "default";
case Token.WHILE: return "while";
case Token.DO: return "do";
case Token.FOR: return "for";
case Token.BREAK: return "break";
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> node.getIntProp(INCRDECR_PROP, 0);
if (post1 != post2)
return false;
} else if (type == Token.STRING) {
int quoted1 = this.getIntProp(QUOTED_PROP, 0);
int quoted2 = node.getIntProp(QUOTED_PROP, 0);
if (quoted1 != quoted2)
return false;
}
return true;
}
public boolean hasSideEffects()
{
switch (type) {
case Token.EXPR_VOID:
case Token.COMMA:
if (last != null)
return last.hasSideEffects();
else
return true;
case Token.HOOK:
if (first == null ||
first.next == null ||
first.next.next == null)
Kit.codeBug();
return first.next.hasSideEffects() &&
first.next.next.hasSideEffects();
case Token.ERROR: // Avoid cascaded error messages
case Token.EXPR_RESULT:
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ENTERWITH:
case Token.LEAVEWITH:
case Token.RETURN:
case Token.GOTO:
case Token.IFEQ:
case Token.IFNE:
case Token.NEW:
case Token.DELPROP:
case Token.SETNAME:
case Token.SETPROP:
case Token.SETELEM:
case Token.CALL:
case Token.THROW:
case Token.RETHROW:
case Token.SETVAR:
case Token.CATCH_SCOPE:
case Token.RETURN_RESULT:
case Token.SET_REF:
case Token.DEL_REF:
case Token.REF_CALL:
case Token.TRY:
case Token.SEMI:
case Token.INC:
case Token.DEC:
case Token.EXPORT:
case Token.IMPORT:
case Token.IF
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>scopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
//==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null; ) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its
* children.
*/
public Node cloneNode() {
Node result;
try {
result = (Node) super.clone();
result.next = null;
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> node is an optional argument node. This
* method is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public void setOptionalArg(boolean optionalArg)
{
putBooleanProp(OPT_ARG_NAME, optionalArg);
}
/**
* Returns whether this node is an optional argument node. This
* method's return value is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public boolean isOptionalArg()
{
return getBooleanProp(OPT_ARG_NAME);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setIsSyntheticBlock(boolean val) {
putBooleanProp(SYNTHETIC_BLOCK_PROP, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean isSyntheticBlock() {
return getBooleanProp(SYNTHETIC_BLOCK_PROP);
}
/**
* Sets the ES5 directives on this node.
*/
public void setDirectives(Set<String> val) {
putProp(DIRECTIVES, val);
}
/**
* Returns the set of ES5 directives for this node.
*/
@SuppressWarnings("unchecked")
public Set<String> getDirectives() {
return (Set<String>) getProp(DIRECTIVES);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setWasEmptyNode(boolean val) {
putBooleanProp(EMPTY_BLOCK, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean wasEmptyNode() {
return getBooleanProp(EMPTY_BLOCK);
}
/**
* Marks this function or constructor call node as having no side effects.
* This property is only meaningful for {@link Token#CALL} and
* {@link Token#NEW} nodes.
*/
public void setIsNoSideEffectsCall() {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> constructor;
InstanceObjectType(JSTypeRegistry registry, FunctionType constructor) {
this(registry, constructor, false);
}
InstanceObjectType(JSTypeRegistry registry, FunctionType constructor,
boolean isNativeType) {
super(registry, null, null, isNativeType);
Preconditions.checkNotNull(constructor);
this.constructor = constructor;
}
@Override
public String getReferenceName() {
return getConstructor().getReferenceName();
}
@Override
public boolean hasReferenceName() {
return getConstructor().hasReferenceName();
}
@Override
public ObjectType getImplicitPrototype() {
return getConstructor().getPrototype();
}
@Override
public FunctionType getConstructor() {
return constructor;
}
@Override
boolean defineProperty(String name, JSType type, boolean inferred,
boolean inExterns) {
ObjectType proto = getImplicitPrototype();
if (proto != null && proto.hasOwnDeclaredProperty(name)) {
return false;
}
return super.defineProperty(name, type, inferred, inExterns);
}
@Override
public String toString() {
return constructor.getReferenceName();
}
@Override
boolean isTheObjectType() {
return getConstructor().isNative() && "Object".equals(getReferenceName());
}
@Override
public boolean isInstanceType() {
return true;
}
@Override
public boolean isArrayType() {
return getConstructor().isNative() && "Array".equals(getReferenceName());
}
@Override
public boolean isStringObjectType() {
return getConstructor().isNative() && "String".equals(getReferenceName());
}
@Override
public boolean isBooleanObjectType() {
return getConstructor().isNative() && "Boolean".equals(getReferenceName());
}
@Override
public boolean isNumberObjectType() {
return getConstructor().isNative() && "Number".equals(getReferenceName());
}
@Override
public boolean isDateType() {
return getConstructor().isNative() && "Date".equals(getReferenceName());
}
@Override
public boolean isRegexpType() {
return getConstructor().isNative() && "RegExp".equals(getReferenceName());
}
@Override
public boolean isNominalType() {
return hasReferenceName();
}
@Override
public boolean equals(Object that) {
if
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> setWarningLevels(CompilerOptions options,
List<String> diagnosticGroups, CheckLevel level) {
for (String name : diagnosticGroups) {
DiagnosticGroup group = forName(name);
Preconditions.checkNotNull(group, "No warning class for name: " + name);
options.setWarningLevel(group, level);
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> map and connect:
* foo() -> bar()
* bar() -> END
*/
private final Multimap<Node, Node> finallyMap = HashMultimap.create();
/**
* Constructor.
*
* @param compiler Compiler instance.
* @param shouldTraverseFunctions Whether functions should be traversed (true
* by default).
*/
ControlFlowAnalysis(AbstractCompiler compiler,
boolean shouldTraverseFunctions) {
this.compiler = compiler;
this.shouldTraverseFunctions = shouldTraverseFunctions;
}
ControlFlowGraph<Node> getCfg() {
return cfg;
}
@Override
public void process(Node externs, Node root) {
this.root = root;
astPositionCounter = 0;
astPosition = Maps.newHashMap();
nodePriorities = Maps.newHashMap();
cfg = new AstControlFlowGraph(computeFallThrough(root), nodePriorities);
NodeTraversal.traverse(compiler, root, this);
astPosition.put(null, ++astPositionCounter); // the implicit return is last.
// Now, generate the priority of nodes by doing a depth-first
// search on the CFG.
priorityCounter = 0;
DiGraphNode<Node, Branch> entry = cfg.getEntry();
prioritizeFromEntryNode(entry);
if (shouldTraverseFunctions) {
// If we're traversing inner functions, we need to rank the
// priority of them too.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
Node value = candidate.getValue();
if (value != null && value.getType() == Token.FUNCTION) {
Preconditions.checkState(
!nodePriorities.containsKey(candidate) || candidate == entry);
prioritizeFromEntryNode(candidate);
}
}
}
// At this point, all reachable nodes have been given a priority, but
// unreachable nodes have not been given a priority. Put them last.
// Presumably, it doesn't really matter what priority they get, since
// this shouldn't happen in real code.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
if (!nodePriorities.containsKey(candidate)) {
nodePriorities.put(candidate, ++priorityCounter);
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
}
}
// Again, the implicit return node is always last.
nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter);
}
/**
* Given an entry node, find all the nodes reachable from that node
* and prioritize them.
*/
private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) {
PriorityQueue<DiGraphNode<Node, Branch>> worklist =
new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator);
worklist.add(entry);
while (!worklist.isEmpty()) {
DiGraphNode<Node, Branch> current = worklist.remove();
if (nodePriorities.containsKey(current)) {
continue;
}
nodePriorities.put(current, ++priorityCounter);
List<DiGraphNode<Node, Branch>> successors =
cfg.getDirectedSuccNodes(current);
for (DiGraphNode<Node, Branch> candidate : successors) {
worklist.add(candidate);
}
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
astPosition.put(n, astPositionCounter++);
switch (n.getType()) {
case Token.FUNCTION:
if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) {
exceptionHandler.push(n);
return true;
}
return false;
case Token.TRY:
exceptionHandler.push(n);
return true;
}
/*
* We are going to stop the traversal depending on what the node's parent
* is.
*
* We are only interested in adding edges between nodes that change control
* flow. The most obvious ones are loops and IF-ELSE's. A statement
* transfers control to its next sibling.
*
* In case of an expression tree, there is no control flow within the tree
* even when there are short circuited operators and conditionals. When we
* are doing data flow analysis, we will simply synthesize lattices up the
* expression tree by finding the meet at each expression node.
*
* For example: within a Token.SWITCH, the expression in question does not
* change the control flow and need not to be considered.
*/
if (parent !=
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> null) {
switch (parent.getType()) {
case Token.FOR:
// Only traverse the body of the for loop.
return n == parent.getLastChild();
// Skip the conditions.
case Token.IF:
case Token.WHILE:
case Token.WITH:
return n != parent.getFirstChild();
case Token.DO:
return n != parent.getFirstChild().getNext();
// Only traverse the body of the cases
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handle
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>, Branch.UNCOND, computeFollowNode(node));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.getType() == Token.CASE);
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.getType() == Token.BLOCK && parent != null &&
parent.getType() == Token.TRY &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> control doesn't go into that
// function (unless it is called)
while (child != null && child.getType() == Token.FUNCTION) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.getType() == Token.BLOCK && node.isSyntheticBlock()) {
Node next = node.getLastChild();
if (next != null) {
createEdge(node, Branch.SYN_BLOCK, computeFallThrough(next));
}
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
*
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> discover a FINALLY, we will connect the BREAK to that FINALLY. From then
* on, we will just record the control flow changes in the finallyMap. This
* is due to the fact that we need to connect any node that leaves its own
* FINALLY block to the outer FINALLY or the BREAK's target but those nodes
* are not known yet due to the way we traverse the nodes.
*/
for (cur = node, lastJump = node;
!isBreakTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFallThrough(
cur.getLastChild()));
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find break target.");
}
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur));
} else {
finallyMap.put(lastJump, computeFollowNode(cur));
}
}
private void handleContinue(Node node) {
String label = null;
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node lastJump;
// Similar to handBreak's logic with a few minor variation.
Node parent = node.getParent();
for (cur = node, lastJump = node;
!isContinueTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, cur.getLastChild());
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find continue target.");
}
Node iter = cur;
if (cur.getChildCount() == 4) {
iter = cur.getFirstChild().getNext().getNext
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>3. If the node is a return statement, we should also transfer control
* back to the caller of the function.
*
* 4. If the node is root then we have reached the end of what we have been
* asked to traverse.
*
* In all cases we should transfer control to a "symbolic return" node.
* This will make life easier for DFAs.
*/
Node parent = node.getParent();
if (parent == null || parent.getType() == Token.FUNCTION || node == root) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent);
case Token.CASE:
case Token.DEFAULT:
// After the body of a CASE, the control goes to the body of the next
// case, without having to go to the case condition.
if (parent.getNext() != null) {
if (parent.getNext().getType() == Token.CASE) {
return parent.getNext().getFirstChild().getNext();
} else if (parent.getNext().getType() == Token.DEFAULT) {
return parent.getNext().getFirstChild();
} else {
Preconditions.checkState(false, "Not reachable");
}
} else {
return computeFollowNode(fromNode, parent);
}
break;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
return parent;
} else {
return parent.getFirstChild().getNext().getNext();
}
case Token.WHILE:
case Token.DO:
return parent;
case Token.TRY:
// If we are coming out of the TRY block...
if (parent.getFirstChild() == node) {
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(parent.getLastChild());
} else { // and have no FINALLY.
return computeFollowNode(fromNode, parent);
}
// CATCH block.
} else if (NodeUtil.getCatchBlock(parent) == node){
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
return computeFallThrough(node.getNext());
} else {
return computeFollowNode(fromNode, parent);
}
// If we are coming out of the FINALLY block...
} else if (parent.getLastChild() == node){
for (Node finallyNode : finallyMap.get(parent)) {
createEdge(fromNode, Branch.UNCOND, finallyNode);
}
return computeFollowNode(fromNode, parent);
}
}
// Now that we are done with the special cases follow should be its
// immediate sibling, unless its sibling is a function
Node nextSibling = node.getNext();
// Skip function declarations because control doesn't get pass into it.
while (nextSibling != null && nextSibling.getType() == Token.FUNCTION) {
nextSibling = nextSibling.getNext();
}
if (nextSibling != null) {
return computeFallThrough(nextSibling);
} else {
// If there are no more siblings, control is transfered up the AST.
return computeFollowNode(fromNode, parent);
}
}
/**
* Computes the destination node of n when we want to fallthough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
private static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n;
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (NodeUtil.isFunction(handler)) {
return;
}
Preconditions.checkState(handler.getType() == Token.TRY);
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
private static boolean isBreakTarget(
Node target, Node parent, String label) {
return isBreakStructure(target, label != null) && matchLabel(parent, label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.getType() == Token.LABEL) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
private static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* A {@link ControlFlowGraph} which provides a node comparator based on the
* pre-order traversal of the AST.
*/
private static class AstControlFlowGraph extends ControlFlowGraph<Node> {
private final Map<DiGraphNode<Node, Branch>, Integer> priorities;
/**
* Constructor.
* @param entry The entry node.
* @param
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> priorities The map from nodes to position in the AST (to be
* filled by the {@link ControlFlowAnalysis#shouldTraverse}).
*/
private AstControlFlowGraph(Node entry,
Map<DiGraphNode<Node, Branch>, Integer> priorities) {
super(entry);
this.priorities = priorities;
}
@Override
/**
* Returns a node comparator based on the pre-order traversal of the AST.
* @param isForward x 'before' y in the pre-order traversal implies
* x 'less than' y (if true) and x 'greater than' y (if false).
*/
public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator(
boolean isForward) {
if (isForward) {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n1) - getPosition(n2);
}
};
} else {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n2) - getPosition(n1);
}
};
}
}
/**
* Gets the pre-order traversal position of the given node.
* @return An arbitrary counter used for comparing positions.
*/
private int getPosition(DiGraphNode<Node, Branch> n) {
Integer priority = priorities.get(n);
Preconditions.checkNotNull(priority);
return priority;
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> instanceof ObjectType) &&
!(enumTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
if (typeList != null &&
("Array".equals(n.getString()) ||
"Object".equals(n.getString()))) {
JSType parameterType =
createFromTypeNodes(
typeList.getLastChild(), sourceName, scope);
namedType = new ParameterizedType(
this, (ObjectType) namedType, parameterType);
if (typeList.hasMoreThanOneChild()) {
JSType indexType =
createFromTypeNodes(
typeList.getFirstChild(), sourceName, scope);
namedType = new IndexedType(
this, (ObjectType) namedType, indexType);
}
}
return createNullableType(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS) {
Node thisNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodes(thisNode, sourceName, scope)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.thisnotobject"),
sourceName, thisNode.getLineno(), "", thisNode.getCharno());
}
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.LP) {
Node args = current.getFirstChild();
for (Node arg = current.getFirstChild(); arg != null;
arg = arg.getNext()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodes(
arg.getFirstChild(), sourceName, scope));
}
} else {
JSType type = createFromTypeNodes(arg, sourceName, scope);
if (arg.getType() == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>EnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
case Token.FINALLY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// is bleed into the local scope and parameters has been assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body represent by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// Theses control structure is represented by its node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structure in that
// it has a initialization and a increment statement. Those
// two statements have its corresponding CFG nodes to represent them.
// The FOR node represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
if (NodeUtil.isForIn(parent)) {
return n == parent.getLastChild();
} else {
return NodeUtil.getConditionExpression(parent) != n;
}
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
}
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Norris Boyd
* Roger Lawrence
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
public class FunctionNode extends ScriptOrFnNode {
private static final long serialVersionUID = 1L;
public FunctionNode(String name) {
super(Token.FUNCTION);
functionName = name;
}
public FunctionNode(String name, int lineno, int charno) {
super(Token.FUNCTION, lineno, charno);
functionName = name;
}
public String getFunctionName() {
return
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> null;
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're builidng.
*/
final Scope scope;
/**
* The current source file that we're in.
*/
private String sourceName = null;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (n.getType() == Token.FUNCTION ||
n.getType() == Token.SCRIPT) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild() || parent == scope.getRootNode();
}
@Override
public abstract void visit(NodeTraversal t, Node n, Node parent);
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
JSType getDeclaredTypeInAnnotation(
NodeTraversal t, Node node, JSDocInfo info) {
return getDeclaredTypeInAnnotation(t.getSourceName(), node, info);
}
JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode = node.getType() == Token.GETPROP ?
node.getFirstChild() : null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
// constructors are often handled separately.
if (info.isConstructor() && typeRegistry.getType(fnName) != null) {
return null;
}
FunctionTypeBuilder builder =
new FunctionTypeBuilder(
fnName, compiler, node, sourceName, scope)
.inferTemplateTypeName(info
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>)
.inferReturnType(info)
.inferParameterTypes(info)
.inferInheritance(info);
// Infer the context type.
boolean searchedForThisType = false;
if (objNode != null) {
if (objNode.getType() == Token.GETPROP &&
objNode.getLastChild().getString().equals("prototype")) {
builder.inferThisType(info, objNode.getFirstChild());
searchedForThisType = true;
} else if (objNode.getType() == Token.THIS) {
builder.inferThisType(info, objNode.getJSType());
searchedForThisType = true;
}
}
if (!searchedForThisType) {
builder.inferThisType(info, (Node) null);
}
jsType = builder.buildAndRegister();
}
}
return jsType;
}
/**
* Defines variable(s) or modifies types based on the content of the node
* {@code n}. A variable definition creates variables in the current scope,
* a function definition creates a binding, and an assignment updates the
* type of the namespaces on which the definition is made (e.g.
* {@code goog.FOO = 6}).
* @param n a {@link Token#VAR}, {@link Token#FUNCTION} or
* {@link Token#ASSIGN} node
* @param parent {@code n}'s parent
*/
void define(Node n, Node parent) {
Preconditions.checkState(sourceName != null);
JSDocInfo info = n.getJSDocInfo();
switch (n.getType()) {
case Token.CATCH:
Node catchName = n.getFirstChild();
defineSlot(catchName, n, null);
break;
case Token.VAR:
if (n.getChildCount() > 1) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSD
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>ocInfo());
}
break;
case Token.FUNCTION:
int parentType = parent.getType();
Preconditions.checkState(
(scope.isLocal() || parentType != Token.ASSIGN) &&
parentType != Token.NAME,
"function defined as standalone function when it is being " +
"assigned");
String functionName = n.getFirstChild().getString();
FunctionType functionType = getFunctionType(functionName, n, info,
null);
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
break;
case Token.ASSIGN:
// TODO(nicksantos): We should support direct assignment to a
// prototype, as in:
// Foo.prototype = {
// a: function() { ... },
// b: function() { ... }
// };
// Right now (6/23/08), we understand most of this syntax, but we
// don't tie the "a" and "b" methods to the context of Foo.
Node rvalue = n.getLastChild();
Node lvalue = n.getFirstChild();
info = (info != null) ? info : rvalue.getJSDocInfo();
if (rvalue.getType() == Token.FUNCTION ||
info != null && info.isConstructor()) {
getFunctionType(lvalue.getQualifiedName(), rvalue, info,
lvalue);
} else if (info != null && info.hasEnumParameterType()) {
lvalue.setJSType(
getEnumType(lvalue.getQualifiedName(), n, rvalue,
info.getEnumParameterType().evaluate(scope)));
}
break;
default:
throw new IllegalStateException(Integer.toString(n.getType()));
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
* @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param parent {@code var}'s parent.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, Node
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> parent, JSDocInfo info) {
Node value = name.getFirstChild();
if (value != null && value.getType() == Token.FUNCTION) {
// function
String functionName = name.getString();
FunctionType functionType =
getFunctionType(functionName, value, info, null);
defineSlot(name, var, functionType);
} else {
// variable's type
JSType type = null;
if (info == null) {
// the variable's type will be inferred
CompilerInput input = compiler.getInput(sourceName);
Preconditions.checkNotNull(input, sourceName);
type = input.isExtern() ?
typeRegistry.getNativeType(UNKNOWN_TYPE) : null;
} else if (info.hasEnumParameterType()) {
type = getEnumType(name.getString(), var, value,
info.getEnumParameterType().evaluate(scope));
} else if (info.isConstructor()) {
type = getFunctionType(name.getString(), value, info, name);
} else {
type = getDeclaredTypeInAnnotation(sourceName, name, info);
}
defineSlot(name, var, type);
}
}
/**
* Gets the function type from the function node and its attached
* {@link JSDocInfo}.
* @param name the function's name
* @param rValue the function node. It must be a {@link Token#FUNCTION}.
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being assigned to a qualified name.
*/
private FunctionType getFunctionType(String name,
Node rValue, JSDocInfo info, @Nullable Node lvalueNode) {
FunctionType functionType = null;
// Handle function aliases.
if (rValue != null && rValue.isQualifiedName()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof FunctionType) {
functionType = (FunctionType) var.getType();
if (functionType != null && function
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>Type.isConstructor()) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
return functionType;
}
Node owner = null;
if (lvalueNode != null) {
owner = getPrototypePropertyOwner(lvalueNode);
}
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.getType() == Token.FUNCTION;
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
if (functionType == null && info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = (FunctionType) type;
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
if (info == null ||
!FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
// We don't really have any type information in the annotation.
// Before we give up on this function, look at the object we're
// assigning it to. For example, if the function looks like this:
// SubFoo.prototype.bar = function() { ... };
// We can use type information on Foo.prototype.bar and apply it
// to this function.
if (lvalueNode != null && lvalueNode.getType() == Token.GETPROP &&
lvalueNode.isQualifiedName()) {
Var var = scope.getVar(
lvalueNode.getFirstChild().getQualifiedName());
if (var != null) {
ObjectType ownerType = ObjectType.cast(var.getType());
FunctionType propType = null;
if (ownerType != null) {
propType = findOverriddenFunction(
ownerType, lvalueNode.getLastChild().getString());
}
if (propType != null) {
functionType =
new FunctionTypeBuilder(
name, compiler, errorRoot, sourceName, scope)
.setSourceNode(fnRoot)
.inferFromOverriddenFunction(propType, parametersNode)
.inferThisType(
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>TypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualfied names.
boolean shouldDeclareOnGlobalThis = false;
if (n.getType() == Token.NAME) {
Preconditions.checkArgument(
parent.getType() == Token.FUNCTION ||
parent.getType() == Token.VAR ||
parent.getType() == Token.LP ||
parent.getType() == Token.CATCH);
shouldDeclareOnGlobalThis = scope.isGlobal() &&
(parent.getType() == Token.VAR ||
parent.getType() == Token.FUNCTION);
} else {
Preconditions.checkArgument(
n.getType() == Token.GETPROP &&
(parent.getType() == Token.ASSIGN ||
parent.getType() == Token.EXPR_RESULT));
}
String variableName = n.getQualifiedName();
Preconditions.checkArgument(!variableName.isEmpty());
// declared in closest scope?
if (scope.isDeclared(variableName, false)) {
Var oldVar = scope.getVar(variableName);
validator.expectUndeclaredVariable(
sourceName, n, parent, oldVar, variableName, type);
} else {
if (!inferred) {
n.setJSType(type);
}
CompilerInput input = compiler.getInput(sourceName);
scope.declare(variableName, n, type, input, inferred);
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS);
boolean isExtern = input.isExtern();
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
typeRegistry.getNativeType(JSTypeNative.NO_TYPE) :
type,
isExtern);
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>getReferenceName(), null, null,
null);
delegateProxy.setPrototypeBasedOn(delegateBaseCtor);
codingConvention.applyDelegateRelationship(
delegateSuperObject, delegateBaseObject, delegatorObject,
delegateProxy, findDelegate);
delegateProxyMap.put(
delegateProxy.getPrototype(),
delegateBaseCtor.getPrototype());
}
}
}
ObjectLiteralCast objectLiteralCast =
codingConvention.getObjectLiteralCast(t, n);
if (objectLiteralCast != null) {
ObjectType type = ObjectType.cast(
typeRegistry.getType(objectLiteralCast.typeName));
if (type != null && type.getConstructor() != null) {
objectLiteralCast.objectNode.setJSType(type);
} else {
compiler.report(JSError.make(t.getSourceName(), n,
CONSTRUCTOR_EXPECTED));
}
}
break;
case Token.FUNCTION:
if (!t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// VARs and ASSIGNs are handled separately.
if (parent.getType() == Token.ASSIGN ||
parent.getType() == Token.NAME) {
return;
}
define(n, parent);
break;
case Token.ASSIGN:
// Handle constructor and enum definitions.
define(n, parent);
// Handle typedefs.
checkForOldStyleTypedef(t, n);
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.getType() == Token.GETPROP &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
define(n, parent);
break;
case Token.VAR:
define(n, parent);
// Handle typedefs.
if (n.hasOneChild()) {
checkForOldStyleTypedef(t, n);
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.getType() == Token.EXPR_RESULT &&
n.isQualifiedName()) {
checkForTypedef(t, n, n.get
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.declareType(typedef, realType);
// Duplicate typedefs get handled when we try to register
// this typedef in the scope.
}
}
/**
* Declare the symbol for a qualified name in the global scope.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param parent The parent of {@code n}.
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
Node ownerNode = n.getFirstChild();
String ownerName = ownerNode.getQualifiedName();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
Preconditions.checkArgument(qName != null && ownerName != null);
// Function prototypes are special.
// It's a common JS idiom to do:
// F.prototype = { ... };
// So if F does not have an explicitly declared super type,
// allow F.prototype to be redefined arbitrarily.
if ("prototype".equals(propName)) {
Var qVar = scope.getVar(qName);
if (qVar != null) {
if (!qVar.isTypeInferred()) {
// Just ignore assigns to declared prototypes.
return;
}
scope.undeclare(qVar);
}
}
// Precedence of type information on GETPROPs:
// 1) @type annotation / @enum annotation
// 2) ASSIGN to FUNCTION literal
// 3) @param/@return annotation (with no function literal)
// 4) ASSIGN to anything else
//
// 1 and 3 are declarations, 4 is inferred, and 2 is a declaration iff
// the function has not been declared before.
//
// FUNCTION literals are special because TypedScopeCreator is very smart
// about getting as much type information as possible for them.
// Determining type for
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> #1 + #2 + #3
JSType valueType = getDeclaredGetPropType(t, info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #4
valueType = rhsValue.getJSType();
}
if (valueType == null) {
if (parent.getType() == Token.EXPR_RESULT) {
stubDeclarations.add(new StubDeclaration(
n, t.getInput().isExtern(), ownerName));
} else if (rhsValue != null &&
rhsValue.getType() == Token.TRUE) {
// We declare these for delegate proxy method properties.
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType instanceof FunctionType) {
JSType ownerTypeOfThis = ((FunctionType) ownerType).getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, typeRegistry.getNativeType(BOOLEAN_TYPE),
true);
}
}
}
return;
}
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3
inferred = !(info.hasType() || info.hasEnumParameterType() ||
FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred) {
// Determining declaration for #2
inferred = !(rhsValue != null &&
rhsValue.getType() == Token.FUNCTION &&
!scope.isDeclared(qName, false));
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
ownerType.defineDeclaredProperty(propName,
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> valueType, isExtern);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
defineSlot(n, parent, valueType, inferred);
}
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Look for a type declaration on a GETPROP node.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredGetPropType(NodeTraversal t, JSDocInfo info,
Node n, Node rhsValue) {
if (info != null && info.hasType()) {
return getDeclaredTypeInAnnotation(t, n, info);
} else if (info != null && info.hasEnumParameterType()) {
return n.getJSType();
} else if (rhsValue != null &&
rhsValue.getType() == Token.FUNCTION) {
return rhsValue.getJSType();
} else {
return getDeclaredTypeInAnnotation(t, n, info);
}
}
/**
* Resolve any stub delcarations to unknown types if we could not
* find types for them during traversal.
*/
private void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>
// if the member expression is not of the form: this.someProperty.
if (info == null ||
member.getType() != Token.GETPROP ||
member.getFirstChild().getType() != Token.THIS) {
return;
}
member.getFirstChild().setJSType(thisType);
JSType jsType = getDeclaredTypeInAnnotation(t, member, info);
Node name = member.getLastChild();
if (jsType != null &&
(name.getType() == Token.NAME || name.getType() == Token.STRING)) {
thisType.defineDeclaredProperty(
name.getString(),
jsType,
false /* functions with implementations are not in externs */);
}
}
} // end CollectProperties
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're builidng.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) return;
if (n.getType() == Token.LP && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
switch (n.getType()) {
case Token.FUNCTION:
// VARs and ASSIGNs are handled separately.
if (parent.getType() == Token.NAME) {
return;
}
define(n, parent);
break;
case Token.CATCH:
case Token.VAR:
define(n, parent);
break;
}
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleed
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> null} to indicate
* that the return type is unknown.
* @param typeOfThis The type of {@code this} in non-constructors. May be
* {@code null} to indicate that the type of {@code this} is unknown.
* @param templateTypeName The template type name or {@code null}.
*/
public FunctionType(JSTypeRegistry registry, String name, Node source,
Node parameters, JSType returnType, ObjectType typeOfThis,
String templateTypeName) {
this(registry, name, source, parameters, returnType, typeOfThis,
templateTypeName, false, false);
}
/** Creates an instance for a function that might be a constructor. */
FunctionType(JSTypeRegistry registry, String name, Node source,
Node parameters, JSType returnType, ObjectType typeOfThis,
String templateTypeName, boolean isConstructor, boolean nativeType) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
this.source = source;
this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY;
if (isConstructor) {
this.typeOfThis = typeOfThis != null && typeOfThis.isNoObjectType() ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
// The call type should be set up last because we are calling getReturnType,
// which may be overloaded and depend on other properties being set.
this.call = new ArrowType(registry, parameters,
(returnType == null ? getReturnType() : returnType));
this.templateTypeName = templateTypeName;
}
/** Creates an instance for a function that is an interface. */
FunctionType(JSTypeRegistry registry, String name, Node source) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE));
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = null;
this
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>);
if (functionInstance.equals(that)) {
return that;
} else if (functionInstance.equals(this)) {
return this;
}
return registry.getNativeType(JSTypeNative.U2U_CONSTRUCTOR_TYPE);
}
return super.getLeastSupertype(that);
}
@Override
public JSType getGreatestSubtype(JSType that) {
if (isFunctionType() && that.isFunctionType()) {
if (equals(that)) {
return this;
}
JSType functionInstance = registry.getNativeType(
JSTypeNative.FUNCTION_INSTANCE_TYPE);
if (functionInstance.equals(that)) {
return this;
} else if (functionInstance.equals(this)) {
return that;
}
return registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE);
}
return super.getGreatestSubtype(that);
}
/**
* Given a constructor or an interface type, get its superclass constructor
* or {@code null} if none exists.
*/
public FunctionType getSuperClassConstructor() {
Preconditions.checkArgument(isConstructor() || isInterface());
ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return null;
}
return maybeSuperInstanceType.getConstructor();
}
/**
* Given a constructor or an interface type, find out whether the unknown
* type is a supertype of the current type.
*/
public boolean hasUnknownSupertype() {
Preconditions.checkArgument(isConstructor() || isInterface());
Preconditions.checkArgument(!this.isUnknownType());
// Potential infinite loop if our type system messes up or someone defines
// a bad type. Otherwise the loop should always end.
FunctionType ctor = this;
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS> * Given a constructor or an interface type and a property, finds the
* top-most superclass that has the property defined (including this
* constructor).
*/
public JSType getTopMostDefiningType(String propertyName) {
Preconditions.checkState(isConstructor() || isInterface());
Preconditions.checkArgument(getPrototype().hasProperty(propertyName));
FunctionType ctor = this;
JSType topInstanceType;
do {
topInstanceType = ctor.getInstanceType();
ctor = ctor.getSuperClassConstructor();
} while (ctor != null && ctor.getPrototype().hasProperty(propertyName));
return topInstanceType;
}
/**
* Two function types are equal if their signatures match. Since they don't
* have signatures, two interfaces are equal if their names match.
*/
@Override
public boolean equals(Object otherType) {
if (!(otherType instanceof FunctionType)) {
return false;
}
FunctionType that = (FunctionType) otherType;
if (!that.isFunctionType()) {
return false;
}
if (this.isConstructor()) {
if (that.isConstructor()) {
return this == that;
}
return false;
}
if (this.isInterface()) {
if (that.isInterface()) {
return this.getReferenceName().equals(that.getReferenceName());
}
return false;
}
if (that.isInterface()) {
return false;
}
return this.typeOfThis.equals(that.typeOfThis) &&
this.call.equals(that.call);
}
@Override
public int hashCode() {
return isInterface() ? getReferenceName().hashCode() : call.hashCode();
}
public boolean hasEqualCallType(FunctionType otherType) {
return this.call.equals(otherType.call);
}
/**
* Informally, a function is represented by
* {@code function (params): returnType} where the {@code params} is a comma
* separated list of types, the first one being a special
* {@code this:T} if the function expects a known type for {@code this}.
*/
@Override
public String toString() {
if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return "Function";
}
StringBuilder b =
Closure, 141
<FILEB>
<CHANGES>
case Token.FUNCTION:
<CHANGEE>
<CHANGES>
Preconditions.checkState(NodeUtil.isAnonymousFunction(n));
return false;
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
if (NodeUtil.isGetProp(name) || NodeUtil.isName(name)) {
<CHANGEE>
<CHANGES>
<CHANGEE>
<CHANGES>
} else if (name.getType() == Token.OR || name.getType() == Token.HOOK) {
Node firstVal;
if (name.getType() == Token.HOOK) {
firstVal = name.getFirstChild().getNext();
} else {
firstVal = name.getFirstChild();
}
Collection<Definition> defs1 = getCallableDefinitions(definitionProvider,
firstVal);
Collection<Definition> defs2 = getCallableDefinitions(definitionProvider,
firstVal.getNext());
if (defs1!= null && defs2!= null) {
defs1.addAll(defs2);
return defs1;
} else {
return null;
}
} else {
return null;
}
<CHANGEE>
<FILEE>
<FILEB>
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !NodeUtil.isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
<CHANGES>
<CHANGEE>
// Anonymous functions definitions are not changed by side-effects,
// and named functions are not part of expressions.
<CHANGES>
<CHANGEE>
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
<FILEE>
<FILEB>
functionInfo.toString() +
" Calls: " + depFunctionNames + "\n");
}
return sb.toString();
}
/**
* Query the DefinitionProvider for the list of definitions that
* correspond to a given qualified name subtree. Return null if
* DefinitionProvider does not contain an entry for a given name,
* one or more of the values returned by getDeclarations is not
* callable, or the "name" node is not a GETPROP or NAME.
*
* @param definitionProvider The name reference graph
* @param name Query node
* @return non-empty definition list or null
*/
private static Collection<Definition> getCallableDefinitions(
DefinitionProvider definitionProvider, Node name) {
<CHANGES>
<CHANGEE>
List<Definition> result = Lists.newArrayList();
<CHANGES>
if (!NodeUtil.isGetProp(name) && !NodeUtil.isName(name)) {
return null;
}
<CHANGEE>
Collection<Definition> decls =
definitionProvider.getDefinitionsReferencedAt(name);
<SCANS>.isInterface()) {
// An interface function cannot be assigned to anything.
return false;
}
// If functionA is a subtype of functionB, then their "this" types
// should be contravariant. However, this causes problems because
// of the way we enforce overrides. Because function(this:SubFoo)
// is not a subtype of function(this:Foo), our override check treats
// this as an error. It also screws up out standard method
// for aliasing constructors. Let's punt on all this for now.
// TODO(nicksantos): fix this.
FunctionType other = (FunctionType) that;
return (this.isConstructor() || other.isConstructor() ||
other.typeOfThis.isSubtype(this.typeOfThis) ||
this.typeOfThis.isSubtype(other.typeOfThis)) &&
this.call.isSubtype(other.call);
}
if (that instanceof UnionType) {
UnionType union = (UnionType) that;
for (JSType element : union.alternates) {
if (this.isSubtype(element)) {
return true;
}
}
}
return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that);
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseFunctionType(this);
}
/**
* Gets the type of instance of this function.
* @throws IllegalStateException if this function is not a constructor
* (see {@link #isConstructor()}).
*/
public ObjectType getInstanceType() {
Preconditions.checkState(hasInstanceType());
return typeOfThis;
}
/** Sets the instance type. This should only be used for special native types. */
void setInstanceType(ObjectType instanceType) {
typeOfThis = instanceType;
}
/**
* Returns whether this function type has an instance type.
*/
public boolean hasInstanceType() {
return isConstructor() || isInterface();
}
/**
* Gets the type of {@code this} in this function.
*/
public ObjectType getTypeOfThis() {
return typeOfThis.isNoObjectType() ?
registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE) : typeOfThis;
}
/**